17.NOR FLASH驱动

举报
嵌入式与Linux那些事 发表于 2022/03/28 22:39:17 2022/03/28
【摘要】 NOR FLASH硬件原理参考:https://blog.csdn.net/qq_16933601/article/details/102653367 一、内核NOR FLASH驱动框架分析 1.ph...

NOR FLASH硬件原理参考:https://blog.csdn.net/qq_16933601/article/details/102653367

一、内核NOR FLASH驱动框架分析

1.physmap_init

static int __init physmap_init(void)
{
	int err;

	err = platform_driver_register(&physmap_flash_driver);
#ifdef PHYSMAP_COMPAT
	if (err == 0)
		platform_device_register(&physmap_flash);
#endif

	return err;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2.physmap_flash

static struct platform_device physmap_flash = {
	.name		= "physmap-flash",
	.id		= 0,
	.dev		= {
		.platform_data	= &physmap_flash_data,
	},
	.num_resources	= 1,
	.resource	= &physmap_flash_resource,
};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3.physmap_flash_resource

static struct resource physmap_flash_resource = {
	.start		= CONFIG_MTD_PHYSMAP_START,//nor flash的物理基地址
	.end		= CONFIG_MTD_PHYSMAP_START + CONFIG_MTD_PHYSMAP_LEN - 1,//nor flash的容量长度
	.flags		= IORESOURCE_MEM,
};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

4.physmap_flash_driver

static struct platform_driver physmap_flash_driver = {
	.probe		= physmap_flash_probe,
	.remove		= physmap_flash_remove,
#ifdef CONFIG_PM
	.suspend	= physmap_flash_suspend,
	.resume		= physmap_flash_resume,
	.shutdown	= physmap_flash_shutdown,
#endif
	.driver		= {
		.name	= "physmap-flash",
	},
};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注册平台,平台devices里面含有配置信息,基地址,位宽,长度等

5.physmap_flash_probe

static int physmap_flash_probe(struct platform_device *dev)
{
	struct physmap_flash_data *physmap_data;
	struct physmap_flash_info *info;
	const char **probe_type;
	int err;
	......................................
	/*1. 分配结构体*/
    info = kzalloc(sizeof(struct physmap_flash_info), GFP_KERNEL);
	 /*2.设置map_info 结构体*/
	info->map.name = dev->dev.bus_id;//norflash的名字
	info->map.phys = dev->resource->start;//物理基地址
	info->map.size = dev->resource->end - dev->resource->start + 1;//容量长度
	info->map.bankwidth = physmap_data->width;//nor flash的字节位宽
	info->map.set_vpp = physmap_data->set_vpp;//虚拟地址

	info->map.virt = ioremap(info->map.phys, info->map.size);//映射物理地址
	simple_map_init(&info->map);//简单初始化map_info的其它成员
	/*3. 设置mtd_info 结构体 */
	/*通过probe_type指向的名称来识别芯片,当do_map_probe()函数返回NULL表示没找到*/
	/*当找到对应的芯片mtd_info结构体,便返回给当前的info->mtd */
	probe_type = rom_probe_types;
	info->mtd = do_map_probe(*probe_type, &info->map);//通过do_map_probe ()来识别芯片
	info->mtd->owner = THIS_MODULE;
	add_mtd_partitions(info->mtd, info->parts, err);
	if (info->mtd == NULL) {             //最终还是没找到芯片,便注销之前注册的东西并退出
              dev_err(&dev->dev, "map_probe failed\n");
              err = -ENXIO;
              goto err_out;
       }
 
       info->mtd->owner = THIS_MODULE;  


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

6.rom_probe_types

如何添加分区 ,仿照nor flash 数组划分分区

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   = 0x00200000,
	},
	[3] = {
        .name   = "root",
        .offset = MTDPART_OFS_APPEND,//紧跟上一项地址
        .size   = MTDPART_SIZ_FULL,//剩下的所有
	}
};
//添加分区
add_mtd_partitions(s3c_mtd, s3c_nand_parts, 4);

err_out:
 
       physmap_flash_remove(dev);                      //该函数用来注销之前注册的东西
 
       return err;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

通过上面的代码和注释分析到,和我们上一节的nand flash驱动相似,这里是map_info结构体和mtd_info结构体来完成的,当我们要对nor flash分区就要使用add_mtd_partitions()才行

其中当*probe=="cfi_probe"时:

就会通过do_map_probe(“cfi_probe”,&info->map)来识别芯片

最终会进入drivers/mtd/chips/cfi_probe.c的cfi_probe_chip()函数来进入cfi模式,读取芯片信息

当*probe_type=="jedec_probe"时:

最终会进入drivers/mtd/chips/jedec_probe.c中的jedec_probe_chip()函数来使用读ID命令,通过ID来匹配jedec_table[]数组。

所以注册一个块设备驱动,需要以下步骤:

  1. 分配mtd_info 结构体和map_info 结构体

  2. 设置map_info 结构体

  3. 设置mtd_info 结构体

  4. 使用add_mtd_partitions()或者add_mtd_device()来创建MTD字符/块设备

在这里插入图片描述

7.看下如何识别do_map_probe

struct mtd_info *do_map_probe(const char *name, struct map_info *map)
{
	struct mtd_chip_driver *drv;
	struct mtd_info *ret;

	drv = get_mtd_chip_driver(name);

	if (!drv && !request_module("%s", name))
		drv = get_mtd_chip_driver(name);

	if (!drv)
		return NULL;

	ret = drv->probe(map);

	/* We decrease the use count here. It may have been a
	   probe-only module, which is no longer required from this
	   point, having given us a handle on (and increased the use
	   count of) the actual driver code.
	*/
	module_put(drv->module);

	if (ret)
		return ret;

	return NULL;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

8.get_mtd_chip_driver

static struct mtd_chip_driver *get_mtd_chip_driver (const char *name)
{
	struct list_head *pos;
	struct mtd_chip_driver *ret = NULL, *this;

	spin_lock(&chip_drvs_lock);

	list_for_each(pos, &chip_drvs_list) {//链表存放的chip_drvs_list
		this = list_entry(pos, typeof(*this), list);

		if (!strcmp(this->name, name)) {
			ret = this;
			break;
		}
	}
	if (ret && !try_module_get(ret->module))
		ret = NULL;

	spin_unlock(&chip_drvs_lock);

	return ret;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

9.谁设置list_for_each链表呢

void register_mtd_chip_driver(struct mtd_chip_driver *drv)
{
	spin_lock(&chip_drvs_lock);
	list_add(&drv->list, &chip_drvs_list);
	spin_unlock(&chip_drvs_lock);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

10.谁来调用register_mtd_chip_driver注册呢

cfi_probe.c

static int __init cfi_probe_init(void)
{
	register_mtd_chip_driver(&cfi_chipdrv);
	return 0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

11.看下cfiprobe 做了什么

mtd_do_chip_probe

struct mtd_info *mtd_do_chip_probe(struct map_info *map, struct chip_probe *cp)
{
	struct mtd_info *mtd = NULL;
	struct cfi_private *cfi;

	/* First probe the map to see if we have CFI stuff there. */
	cfi = genprobe_ident_chips(map, cp);//通用枚举

	if (!cfi)
		return NULL;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

12.genprobe_ident_chips

static struct cfi_private *genprobe_ident_chips(struct map_info *map, struct chip_probe *cp)
{
	if (!genprobe_new_chip(map, cp, &cfi)) {

  
 
  • 1
  • 2
  • 3

13.genprobe_new_chip

static int genprobe_new_chip(struct map_info *map, struct chip_probe *cp,
			     struct cfi_private *cfi)
{
	int min_chips = (map_bankwidth(map)/4?:1); /* At most 4-bytes wide. */
	int max_chips = map_bankwidth(map); /* And minimum 1 */
	int nr_chips, type;

	for (nr_chips = max_chips; nr_chips >= min_chips; nr_chips >>= 1) {

		if (!cfi_interleave_supported(nr_chips))
		    continue;

		cfi->interleave = nr_chips;

		/* Minimum device size. Don't look for one 8-bit device
		   in a 16-bit bus, etc. */
		type = map_bankwidth(map) / nr_chips;

		for (; type <= CFI_DEVICETYPE_X32; type<<=1) {
			cfi->device_type = type;

			if (cp->probe_chip(map, 0, NULL, cfi))//cfi_chip_probe
				return 1;
		}
	}
	return 0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
static struct chip_probe cfi_chip_probe = {
	.name		= "CFI",
	.probe_chip	= cfi_probe_chip
};

  
 
  • 1
  • 2
  • 3
  • 4

14.cfi_probe_chip

static int __xipram cfi_probe_chip(struct map_info *map, __u32 base,
				   unsigned long *chip_map, struct cfi_private *cfi)
{
	xip_disable();
	cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); // 进入CFI模式
	cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL);
	cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL);

if (!qry_present(map,base,cfi)) {
		xip_enable(base, map, cfi);
		return 0;
	}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

15.qry_present

static int __xipram qry_present(struct map_info *map, __u32 base,// // 看是否能读出"QRY"
				struct cfi_private *cfi)
{
	int osf = cfi->interleave * cfi->device_type;	// scale factor
	map_word val[3];
	map_word qry[3];

	qry[0] = cfi_build_cmd('Q', map, cfi);
	qry[1] = cfi_build_cmd('R', map, cfi);
	qry[2] = cfi_build_cmd('Y', map, cfi);

	val[0] = map_read(map, base + osf*0x10);//读地址返回的值
	val[1] = map_read(map, base + osf*0x11);
	val[2] = map_read(map, base + osf*0x12);

	if (!map_word_equal(map, qry[0], val[0]))
		return 0;

	if (!map_word_equal(map, qry[1], val[1]))
		return 0;

	if (!map_word_equal(map, qry[2], val[2]))
		return 0;

	return 1; 	// "QRY" found
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

16.do_map_probe(“jedec_probe”, s3c_nor_map);

drv = get_mtd_chip_driver(name)
ret = drv->probe(map); // jedec_probe

static struct mtd_info *jedec_probe(struct map_info *map)
{
	/*
	 * Just use the generic probe stuff to call our CFI-specific
	 * chip_probe routine in all the possible permutations, etc.
	 */
	return mtd_do_chip_probe(map, &jedec_chip_probe);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
struct mtd_info *mtd_do_chip_probe(struct map_info *map, struct chip_probe *cp)
{
	struct mtd_info *mtd = NULL;
	struct cfi_private *cfi;

	/* First probe the map to see if we have CFI stuff there. */
	cfi = genprobe_ident_chips(map, cp);//通用枚举
	
	

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

17.genprobe_ident_chips

static struct cfi_private *genprobe_ident_chips(struct map_info *map, struct chip_probe *cp)
{
	struct cfi_private cfi;
	struct cfi_private *retcfi;
	unsigned long *chip_map;
	int i, j, mapsize;
	int max_chips;

	memset(&cfi, 0, sizeof(cfi))
if (!genprobe_new_chip(map, cp, &cfi)) {
		/* The probe didn't like it */
		printk(KERN_DEBUG "%s: Found no %s device at location zero\n",
		       cp->name, map->name);
		return NULL;

	cp->probe_chip(map, i << cfi.chipshift, chip_map, &cfi);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
static struct chip_probe jedec_chip_probe = {
	.name = "JEDEC",
	.probe_chip = jedec_probe_chip
};

  
 
  • 1
  • 2
  • 3
  • 4

18.jedec_probe_chip最后识别

static int jedec_probe_chip(struct map_info *map, __u32 base,
			    unsigned long *chip_map, struct cfi_private *cfi)
{
	int i;
	enum uaddr uaddr_idx = MTD_UADDR_NOT_SUPPORTED;
	u32 probe_offset1, probe_offset2;

 retry:
	if (!cfi->numchips) {
		uaddr_idx++;

		if (MTD_UADDR_UNNECESSARY == uaddr_idx)
			return 0;

		cfi->addr_unlock1 = unlock_addrs[uaddr_idx].addr1;
		cfi->addr_unlock2 = unlock_addrs[uaddr_idx].addr2;
	}

	/* Make certain we aren't probing past the end of map */
	if (base >= map->size) {
		printk(KERN_NOTICE
			"Probe at base(0x%08x) past the end of the map(0x%08lx)\n",
			base, map->size -1);
		return 0;

  // 解锁
   cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);
   cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL);
                                        
    // 读ID命令
   cfi_send_gen_cmd(0x90, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);                                      
                            
  // 得到厂家ID,设备ID
   cfi->mfr = jedec_read_mfr(map, base, cfi);
    cfi->id = jedec_read_id(map, base, cfi);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

19.jedec_table

static const struct amd_flash_info jedec_table[] = {
	{
		.mfr_id		= MANUFACTURER_AMD,
		.dev_id		= AM29F032B,
		.name		= "AMD AM29F032B",
		.uaddr		= {
			[0] = MTD_UADDR_0x0555_0x02AA /* x8 */
		},
		.DevSize	= SIZE_4MiB,
		.CmdSet		= P_ID_AMD_STD,
		.NumEraseRegions= 1,
		.regions	= {
			ERASEINFO(0x10000,64)
		}
	}, {
		.mfr_id		= MANUFACTURER_AMD,
		.dev_id		= AM29LV160DT,
		.name		= "AMD AM29LV160DT",
		.uaddr		= {
			[0] = MTD_UADDR_0x0AAA_0x0555,  /* x8 */
			[1] = MTD_UADDR_0x0555_0x02AA   /* x16 */
		},
		.DevSize	= SIZE_2MiB,
		.CmdSet		= P_ID_AMD_STD,
		.NumEraseRegions= 4,
		.regions	= {
			ERASEINFO(0x10000,31),//擦除块大小 不同区域擦出快大小不一样
			ERASEINFO(0x08000,1),
			ERASEINFO(0x02000,2),
			ERASEINFO(0x04000,1)
		}
 // 最后和数组比较
    jedec_table     

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

注意: 关于NOR FLASH和2440地址线错位相接:
2440或2410中地址线都是从ADDR2中开始连接的,即A0-ADDR2、An- ADDR(n+2)、这是因为ARM是32位处理器所以它一次处理数据都是以32位为单位的,也就是说它读或者写数据时,地址只能为0x0000_0000、0x0000_0004、0x0000_0008。。。即4字节对齐,因为一般DDR的数据线都为16位,所以为了得到32位的数据,一般都是将2个DDR连在一起,它们的地址相同,所以对已DDR而言是一个地址对应4个字节(因为一个DDR对应2个字节,两个DDR就对因4个字节),但是对于CPU而言一个地址只对应1个字节,所以这里就存在一个地址转换问题,即如何让1字节的地址和4字节的地址对应起来。其实就是让CPU发出的4次地址都取到同一份数据就可以了(0地址对应的是DDR的0地址,1地址对应的是DDR的0地址,2地址对应的还是DDR的0地址,3地址对应的还是DDR的0地址,虽然都是0地址,但是0,1,2,3对应的其实是0地址的内部的不同部分,这部分是是由Memory Controller来控制的,根据0,1,2,3选择0地址内部不同的部分返回给CPU)。即使CPU的0~ 3地址里的数据对应DDR的0地址数据,CPU的4~7地址的数据对应DDR的1地址的数据,所以CPU的0地址对应DDR的0地址,0X04地址对应DDR中1地址,0x08地址对应DDR中2地址,可以看出,DDR的地址刚好是CPU寻址向右移动两位,所以2440或2410中地址线都是从ADDR2开始连接的。
http://bbs.100ask.net/question/8202
http://bbs.100ask.net/question/9740
http://bbs.100ask.net/question/11301
http://bbs.100ask.net/question/10963
https://www.cnblogs.com/losing-1216/p/4885588.html

二、写代码


/*
 * 参考 drivers\mtd\maps\physmap.c
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/partitions.h>
#include <asm/io.h>

static struct map_info *s3c_nor_map;
static struct mtd_info *s3c_nor_mtd;

static struct mtd_partition s3c_nor_parts[] = {
	[0] = {
        .name   = "bootloader_nor",
        .size   = 0x00040000,
		.offset	= 0,
	},
	[1] = {
        .name   = "root_nor",
        .offset = MTDPART_OFS_APPEND,
        .size   = MTDPART_SIZ_FULL,
	}
};


static int s3c_nor_init(void)
{
	/* 1. 分配map_info结构体 */
	s3c_nor_map = kzalloc(sizeof(struct map_info), GFP_KERNEL);;
	
	/* 2. 设置: 物理基地址(phys), 大小(size), 位宽(bankwidth), 虚拟基地址(virt) */
	s3c_nor_map->name = "s3c_nor";
	s3c_nor_map->phys = 0;
	s3c_nor_map->size = 0x1000000; /* >= NOR的真正大小 */
	s3c_nor_map->bankwidth = 2;
	s3c_nor_map->virt = ioremap(s3c_nor_map->phys, s3c_nor_map->size);

	simple_map_init(s3c_nor_map);
	
	/* 3. 使用: 调用NOR FLASH协议层提供的函数来识别 */
	printk("use cfi_probe\n");
	s3c_nor_mtd = do_map_probe("cfi_probe", s3c_nor_map);
	if (!s3c_nor_mtd)
	{
		printk("use jedec_probe\n");
		s3c_nor_mtd = do_map_probe("jedec_probe", s3c_nor_map);
	}

	if (!s3c_nor_mtd)
	{		
		iounmap(s3c_nor_map->virt);
		kfree(s3c_nor_map);
		return -EIO;
	}
	
	/* 4. add_mtd_partitions */
	add_mtd_partitions(s3c_nor_mtd, s3c_nor_parts, 2);
	
	return 0;
}

static void s3c_nor_exit(void)
{
	del_mtd_partitions(s3c_nor_mtd);
	iounmap(s3c_nor_map->virt);
	kfree(s3c_nor_map);
}

module_init(s3c_nor_init);
module_exit(s3c_nor_exit);

MODULE_LICENSE("GPL");

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

文章来源: blog.csdn.net,作者:嵌入式与Linux那些事,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/qq_16933601/article/details/103623919

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。