提交 dcca5cf0 编写于 作者: T Tomi Valkeinen

Merge branch '3.8/vram-conversion' of git://gitorious.org/linux-omap-dss2/linux

Conflicts:
	drivers/video/omap2/dss/Kconfig
	drivers/video/omap2/omapfb/omapfb-ioctl.c
	drivers/video/omap2/omapfb/omapfb-main.c

Merge changes to make omapfb use common dma_alloc, and remove omap's
custom vram allocator.
......@@ -285,7 +285,10 @@ FB0 +-- GFX ---- LCD ---- LCD
Misc notes
----------
OMAP FB allocates the framebuffer memory using the OMAP VRAM allocator.
OMAP FB allocates the framebuffer memory using the standard dma allocator. You
can enable Contiguous Memory Allocator (CONFIG_CMA) to improve the dma
allocator, and if CMA is enabled, you use "cma=" kernel parameter to increase
the global memory area for CMA.
Using DSI DPLL to generate pixel clock it is possible produce the pixel clock
of 86.5MHz (max possible), and with that you get 1280x1024@57 output from DVI.
......@@ -301,11 +304,6 @@ framebuffer parameters.
Kernel boot arguments
---------------------
vram=<size>[,<physaddr>]
- Amount of total VRAM to preallocate and optionally a physical start
memory address. For example, "10M". omapfb allocates memory for
framebuffers from VRAM.
omapfb.mode=<display>:<mode>[,...]
- Default video mode for specified displays. For example,
"dvi:800x400MR-24@60". See drivers/video/modedb.c.
......
......@@ -16,7 +16,6 @@
#include <linux/mm.h>
#include <asm/mach-types.h>
#include <video/omapdss.h>
#include <plat/vram.h>
#include <linux/platform_data/spi-omap2-mcspi.h>
#include "board-rx51.h"
......@@ -87,17 +86,4 @@ static int __init rx51_video_init(void)
}
subsys_initcall(rx51_video_init);
void __init rx51_video_mem_init(void)
{
/*
* GFX 864x480x32bpp
* VID1/2 1280x720x32bpp double buffered
*/
omap_vram_set_sdram_vram(PAGE_ALIGN(864 * 480 * 4) +
2 * PAGE_ALIGN(1280 * 720 * 4 * 2), 0);
}
#else
void __init rx51_video_mem_init(void) { }
#endif /* defined(CONFIG_FB_OMAP2) || defined(CONFIG_FB_OMAP2_MODULE) */
......@@ -34,8 +34,6 @@
#define RX51_GPIO_SLEEP_IND 162
extern void rx51_video_mem_init(void);
static struct gpio_led gpio_leds[] = {
{
.name = "sleep_ind",
......@@ -112,7 +110,6 @@ static void __init rx51_init(void)
static void __init rx51_reserve(void)
{
rx51_video_mem_init();
omap_reserve();
}
......
......@@ -17,7 +17,6 @@
#include <linux/dma-mapping.h>
#include <plat/common.h>
#include <plat/vram.h>
#include <linux/platform_data/dsp-omap.h>
#include <plat/dma.h>
......@@ -25,7 +24,6 @@
void __init omap_reserve(void)
{
omap_vram_reserve_sdram_memblock();
omap_dsp_reserve_sdram_memblock();
omap_secure_ram_reserve_memblock();
omap_barrier_reserve_memblock();
......
......@@ -29,6 +29,7 @@
#include <linux/memblock.h>
#include <linux/io.h>
#include <linux/omapfb.h>
#include <linux/dma-mapping.h>
#include <mach/hardware.h>
#include <asm/mach/map.h>
......@@ -106,7 +107,7 @@ static struct platform_device omap_fb_device = {
.id = -1,
.dev = {
.dma_mask = &omap_fb_dma_mask,
.coherent_dma_mask = ~(u32)0,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &omapfb_config,
},
.num_resources = 0,
......@@ -142,7 +143,7 @@ static struct platform_device omap_fb_device = {
.id = -1,
.dev = {
.dma_mask = &omap_fb_dma_mask,
.coherent_dma_mask = ~(u32)0,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &omapfb_config,
},
.num_resources = 0,
......
/*
* VRAM manager for OMAP
*
* Copyright (C) 2009 Nokia Corporation
* Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef __OMAP_VRAM_H__
#define __OMAP_VRAM_H__
#include <linux/types.h>
extern int omap_vram_add_region(unsigned long paddr, size_t size);
extern int omap_vram_free(unsigned long paddr, size_t size);
extern int omap_vram_alloc(size_t size, unsigned long *paddr);
extern int omap_vram_reserve(unsigned long paddr, size_t size);
extern void omap_vram_get_info(unsigned long *vram, unsigned long *free_vram,
unsigned long *largest_free_block);
#ifdef CONFIG_OMAP2_VRAM
extern void omap_vram_set_sdram_vram(u32 size, u32 start);
extern void omap_vram_reserve_sdram_memblock(void);
#else
static inline void omap_vram_set_sdram_vram(u32 size, u32 start) { }
static inline void omap_vram_reserve_sdram_memblock(void) { }
#endif
#endif
config OMAP2_VRAM
bool
config OMAP2_VRFB
bool
......
obj-$(CONFIG_OMAP2_VRAM) += vram.o
obj-$(CONFIG_OMAP2_VRFB) += vrfb.o
obj-$(CONFIG_OMAP2_DSS) += dss/
......
......@@ -5,18 +5,6 @@ menuconfig OMAP2_DSS
if OMAP2_DSS
config OMAP2_VRAM_SIZE
int "VRAM size (MB)"
range 0 32
default 0
help
The amount of SDRAM to reserve at boot time for video RAM use.
This VRAM will be used by omapfb and other drivers that need
large continuous RAM area for video use.
You can also set this with "vram=<bytes>" kernel argument, or
in the board file.
config OMAP2_DSS_DEBUG
bool "Debug support"
default n
......
......@@ -2,7 +2,6 @@ menuconfig FB_OMAP2
tristate "OMAP2+ frame buffer support"
depends on FB && OMAP2_DSS && !DRM_OMAP
select OMAP2_VRAM
select OMAP2_VRFB if ARCH_OMAP2 || ARCH_OMAP3
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
......
......@@ -31,7 +31,6 @@
#include <video/omapdss.h>
#include <video/omapvrfb.h>
#include <plat/vram.h>
#include "omapfb.h"
......@@ -853,14 +852,15 @@ int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg)
break;
case OMAPFB_GET_VRAM_INFO: {
unsigned long vram, free, largest;
DBG("ioctl GET_VRAM_INFO\n");
omap_vram_get_info(&vram, &free, &largest);
p.vram_info.total = vram;
p.vram_info.free = free;
p.vram_info.largest_free_block = largest;
/*
* We don't have the ability to get this vram info anymore.
* Fill in something that should keep the applications working.
*/
p.vram_info.total = SZ_1M * 64;
p.vram_info.free = SZ_1M * 64;
p.vram_info.largest_free_block = SZ_1M * 64;
if (copy_to_user((void __user *)arg, &p.vram_info,
sizeof(p.vram_info)))
......
......@@ -31,7 +31,6 @@
#include <linux/omapfb.h>
#include <video/omapdss.h>
#include <plat/vram.h>
#include <video/omapvrfb.h>
#include "omapfb.h"
......@@ -1333,24 +1332,25 @@ static void omapfb_free_fbmem(struct fb_info *fbi)
rg = ofbi->region;
WARN_ON(atomic_read(&rg->map_count));
if (rg->paddr)
if (omap_vram_free(rg->paddr, rg->size))
dev_err(fbdev->dev, "VRAM FREE failed\n");
if (rg->token == NULL)
return;
if (rg->vaddr)
iounmap(rg->vaddr);
WARN_ON(atomic_read(&rg->map_count));
if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) {
/* unmap the 0 angle rotation */
if (rg->vrfb.vaddr[0]) {
iounmap(rg->vrfb.vaddr[0]);
omap_vrfb_release_ctx(&rg->vrfb);
rg->vrfb.vaddr[0] = NULL;
}
omap_vrfb_release_ctx(&rg->vrfb);
}
dma_free_attrs(fbdev->dev, rg->size, rg->token, rg->dma_handle,
&rg->attrs);
rg->token = NULL;
rg->vaddr = NULL;
rg->paddr = 0;
rg->alloc = 0;
......@@ -1385,7 +1385,9 @@ static int omapfb_alloc_fbmem(struct fb_info *fbi, unsigned long size,
struct omapfb_info *ofbi = FB2OFB(fbi);
struct omapfb2_device *fbdev = ofbi->fbdev;
struct omapfb2_mem_region *rg;
void __iomem *vaddr;
void *token;
DEFINE_DMA_ATTRS(attrs);
dma_addr_t dma_handle;
int r;
rg = ofbi->region;
......@@ -1400,42 +1402,43 @@ static int omapfb_alloc_fbmem(struct fb_info *fbi, unsigned long size,
size = PAGE_ALIGN(size);
if (!paddr) {
DBG("allocating %lu bytes for fb %d\n", size, ofbi->id);
r = omap_vram_alloc(size, &paddr);
} else {
DBG("reserving %lu bytes at %lx for fb %d\n", size, paddr,
ofbi->id);
r = omap_vram_reserve(paddr, size);
}
WARN_ONCE(paddr,
"reserving memory at predefined address not supported\n");
if (r) {
dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB)
dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING, &attrs);
DBG("allocating %lu bytes for fb %d\n", size, ofbi->id);
token = dma_alloc_attrs(fbdev->dev, size, &dma_handle,
GFP_KERNEL, &attrs);
if (token == NULL) {
dev_err(fbdev->dev, "failed to allocate framebuffer\n");
return -ENOMEM;
}
if (ofbi->rotation_type != OMAP_DSS_ROT_VRFB) {
vaddr = ioremap_wc(paddr, size);
if (!vaddr) {
dev_err(fbdev->dev, "failed to ioremap framebuffer\n");
omap_vram_free(paddr, size);
return -ENOMEM;
}
DBG("allocated VRAM paddr %lx, vaddr %p\n",
(unsigned long)dma_handle, token);
DBG("allocated VRAM paddr %lx, vaddr %p\n", paddr, vaddr);
} else {
if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) {
r = omap_vrfb_request_ctx(&rg->vrfb);
if (r) {
dma_free_attrs(fbdev->dev, size, token, dma_handle,
&attrs);
dev_err(fbdev->dev, "vrfb create ctx failed\n");
return r;
}
vaddr = NULL;
}
rg->paddr = paddr;
rg->vaddr = vaddr;
rg->attrs = attrs;
rg->token = token;
rg->dma_handle = dma_handle;
rg->paddr = (unsigned long)dma_handle;
rg->vaddr = (void __iomem *)token;
rg->size = size;
rg->alloc = 1;
......
......@@ -28,6 +28,8 @@
#endif
#include <linux/rwsem.h>
#include <linux/dma-attrs.h>
#include <linux/dma-mapping.h>
#include <video/omapdss.h>
......@@ -49,6 +51,9 @@ extern bool omapfb_debug;
struct omapfb2_mem_region {
int id;
struct dma_attrs attrs;
void *token;
dma_addr_t dma_handle;
u32 paddr;
void __iomem *vaddr;
struct vrfb vrfb;
......
/*
* VRAM manager for OMAP
*
* Copyright (C) 2009 Nokia Corporation
* Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*#define DEBUG*/
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/seq_file.h>
#include <linux/memblock.h>
#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <asm/setup.h>
#include <plat/vram.h>
#ifdef DEBUG
#define DBG(format, ...) pr_debug("VRAM: " format, ## __VA_ARGS__)
#else
#define DBG(format, ...)
#endif
/* postponed regions are used to temporarily store region information at boot
* time when we cannot yet allocate the region list */
#define MAX_POSTPONED_REGIONS 10
static bool vram_initialized;
static int postponed_cnt;
static struct {
unsigned long paddr;
size_t size;
} postponed_regions[MAX_POSTPONED_REGIONS];
struct vram_alloc {
struct list_head list;
unsigned long paddr;
unsigned pages;
};
struct vram_region {
struct list_head list;
struct list_head alloc_list;
unsigned long paddr;
unsigned pages;
};
static DEFINE_MUTEX(region_mutex);
static LIST_HEAD(region_list);
static struct vram_region *omap_vram_create_region(unsigned long paddr,
unsigned pages)
{
struct vram_region *rm;
rm = kzalloc(sizeof(*rm), GFP_KERNEL);
if (rm) {
INIT_LIST_HEAD(&rm->alloc_list);
rm->paddr = paddr;
rm->pages = pages;
}
return rm;
}
#if 0
static void omap_vram_free_region(struct vram_region *vr)
{
list_del(&vr->list);
kfree(vr);
}
#endif
static struct vram_alloc *omap_vram_create_allocation(struct vram_region *vr,
unsigned long paddr, unsigned pages)
{
struct vram_alloc *va;
struct vram_alloc *new;
new = kzalloc(sizeof(*va), GFP_KERNEL);
if (!new)
return NULL;
new->paddr = paddr;
new->pages = pages;
list_for_each_entry(va, &vr->alloc_list, list) {
if (va->paddr > new->paddr)
break;
}
list_add_tail(&new->list, &va->list);
return new;
}
static void omap_vram_free_allocation(struct vram_alloc *va)
{
list_del(&va->list);
kfree(va);
}
int omap_vram_add_region(unsigned long paddr, size_t size)
{
struct vram_region *rm;
unsigned pages;
if (vram_initialized) {
DBG("adding region paddr %08lx size %d\n",
paddr, size);
size &= PAGE_MASK;
pages = size >> PAGE_SHIFT;
rm = omap_vram_create_region(paddr, pages);
if (rm == NULL)
return -ENOMEM;
list_add(&rm->list, &region_list);
} else {
if (postponed_cnt == MAX_POSTPONED_REGIONS)
return -ENOMEM;
postponed_regions[postponed_cnt].paddr = paddr;
postponed_regions[postponed_cnt].size = size;
++postponed_cnt;
}
return 0;
}
int omap_vram_free(unsigned long paddr, size_t size)
{
struct vram_region *rm;
struct vram_alloc *alloc;
unsigned start, end;
DBG("free mem paddr %08lx size %d\n", paddr, size);
size = PAGE_ALIGN(size);
mutex_lock(&region_mutex);
list_for_each_entry(rm, &region_list, list) {
list_for_each_entry(alloc, &rm->alloc_list, list) {
start = alloc->paddr;
end = alloc->paddr + (alloc->pages >> PAGE_SHIFT);
if (start >= paddr && end < paddr + size)
goto found;
}
}
mutex_unlock(&region_mutex);
return -EINVAL;
found:
omap_vram_free_allocation(alloc);
mutex_unlock(&region_mutex);
return 0;
}
EXPORT_SYMBOL(omap_vram_free);
static int _omap_vram_reserve(unsigned long paddr, unsigned pages)
{
struct vram_region *rm;
struct vram_alloc *alloc;
size_t size;
size = pages << PAGE_SHIFT;
list_for_each_entry(rm, &region_list, list) {
unsigned long start, end;
DBG("checking region %lx %d\n", rm->paddr, rm->pages);
start = rm->paddr;
end = start + (rm->pages << PAGE_SHIFT) - 1;
if (start > paddr || end < paddr + size - 1)
continue;
DBG("block ok, checking allocs\n");
list_for_each_entry(alloc, &rm->alloc_list, list) {
end = alloc->paddr - 1;
if (start <= paddr && end >= paddr + size - 1)
goto found;
start = alloc->paddr + (alloc->pages << PAGE_SHIFT);
}
end = rm->paddr + (rm->pages << PAGE_SHIFT) - 1;
if (!(start <= paddr && end >= paddr + size - 1))
continue;
found:
DBG("found area start %lx, end %lx\n", start, end);
if (omap_vram_create_allocation(rm, paddr, pages) == NULL)
return -ENOMEM;
return 0;
}
return -ENOMEM;
}
int omap_vram_reserve(unsigned long paddr, size_t size)
{
unsigned pages;
int r;
DBG("reserve mem paddr %08lx size %d\n", paddr, size);
size = PAGE_ALIGN(size);
pages = size >> PAGE_SHIFT;
mutex_lock(&region_mutex);
r = _omap_vram_reserve(paddr, pages);
mutex_unlock(&region_mutex);
return r;
}
EXPORT_SYMBOL(omap_vram_reserve);
static int _omap_vram_alloc(unsigned pages, unsigned long *paddr)
{
struct vram_region *rm;
struct vram_alloc *alloc;
list_for_each_entry(rm, &region_list, list) {
unsigned long start, end;
DBG("checking region %lx %d\n", rm->paddr, rm->pages);
start = rm->paddr;
list_for_each_entry(alloc, &rm->alloc_list, list) {
end = alloc->paddr;
if (end - start >= pages << PAGE_SHIFT)
goto found;
start = alloc->paddr + (alloc->pages << PAGE_SHIFT);
}
end = rm->paddr + (rm->pages << PAGE_SHIFT);
found:
if (end - start < pages << PAGE_SHIFT)
continue;
DBG("found %lx, end %lx\n", start, end);
alloc = omap_vram_create_allocation(rm, start, pages);
if (alloc == NULL)
return -ENOMEM;
*paddr = start;
return 0;
}
return -ENOMEM;
}
int omap_vram_alloc(size_t size, unsigned long *paddr)
{
unsigned pages;
int r;
BUG_ON(!size);
DBG("alloc mem size %d\n", size);
size = PAGE_ALIGN(size);
pages = size >> PAGE_SHIFT;
mutex_lock(&region_mutex);
r = _omap_vram_alloc(pages, paddr);
mutex_unlock(&region_mutex);
return r;
}
EXPORT_SYMBOL(omap_vram_alloc);
void omap_vram_get_info(unsigned long *vram,
unsigned long *free_vram,
unsigned long *largest_free_block)
{
struct vram_region *vr;
struct vram_alloc *va;
*vram = 0;
*free_vram = 0;
*largest_free_block = 0;
mutex_lock(&region_mutex);
list_for_each_entry(vr, &region_list, list) {
unsigned free;
unsigned long pa;
pa = vr->paddr;
*vram += vr->pages << PAGE_SHIFT;
list_for_each_entry(va, &vr->alloc_list, list) {
free = va->paddr - pa;
*free_vram += free;
if (free > *largest_free_block)
*largest_free_block = free;
pa = va->paddr + (va->pages << PAGE_SHIFT);
}
free = vr->paddr + (vr->pages << PAGE_SHIFT) - pa;
*free_vram += free;
if (free > *largest_free_block)
*largest_free_block = free;
}
mutex_unlock(&region_mutex);
}
EXPORT_SYMBOL(omap_vram_get_info);
#if defined(CONFIG_DEBUG_FS)
static int vram_debug_show(struct seq_file *s, void *unused)
{
struct vram_region *vr;
struct vram_alloc *va;
unsigned size;
mutex_lock(&region_mutex);
list_for_each_entry(vr, &region_list, list) {
size = vr->pages << PAGE_SHIFT;
seq_printf(s, "%08lx-%08lx (%d bytes)\n",
vr->paddr, vr->paddr + size - 1,
size);
list_for_each_entry(va, &vr->alloc_list, list) {
size = va->pages << PAGE_SHIFT;
seq_printf(s, " %08lx-%08lx (%d bytes)\n",
va->paddr, va->paddr + size - 1,
size);
}
}
mutex_unlock(&region_mutex);
return 0;
}
static int vram_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, vram_debug_show, inode->i_private);
}
static const struct file_operations vram_debug_fops = {
.open = vram_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init omap_vram_create_debugfs(void)
{
struct dentry *d;
d = debugfs_create_file("vram", S_IRUGO, NULL,
NULL, &vram_debug_fops);
if (IS_ERR(d))
return PTR_ERR(d);
return 0;
}
#endif
static __init int omap_vram_init(void)
{
int i;
vram_initialized = 1;
for (i = 0; i < postponed_cnt; i++)
omap_vram_add_region(postponed_regions[i].paddr,
postponed_regions[i].size);
#ifdef CONFIG_DEBUG_FS
if (omap_vram_create_debugfs())
pr_err("VRAM: Failed to create debugfs file\n");
#endif
return 0;
}
arch_initcall(omap_vram_init);
/* boottime vram alloc stuff */
/* set from board file */
static u32 omap_vram_sdram_start __initdata;
static u32 omap_vram_sdram_size __initdata;
/* set from kernel cmdline */
static u32 omap_vram_def_sdram_size __initdata;
static u32 omap_vram_def_sdram_start __initdata;
static int __init omap_vram_early_vram(char *p)
{
omap_vram_def_sdram_size = memparse(p, &p);
if (*p == ',')
omap_vram_def_sdram_start = simple_strtoul(p + 1, &p, 16);
return 0;
}
early_param("vram", omap_vram_early_vram);
/*
* Called from map_io. We need to call to this early enough so that we
* can reserve the fixed SDRAM regions before VM could get hold of them.
*/
void __init omap_vram_reserve_sdram_memblock(void)
{
u32 paddr;
u32 size = 0;
/* cmdline arg overrides the board file definition */
if (omap_vram_def_sdram_size) {
size = omap_vram_def_sdram_size;
paddr = omap_vram_def_sdram_start;
}
if (!size) {
size = omap_vram_sdram_size;
paddr = omap_vram_sdram_start;
}
#ifdef CONFIG_OMAP2_VRAM_SIZE
if (!size) {
size = CONFIG_OMAP2_VRAM_SIZE * 1024 * 1024;
paddr = 0;
}
#endif
if (!size)
return;
size = ALIGN(size, SZ_2M);
if (paddr) {
if (paddr & ~PAGE_MASK) {
pr_err("VRAM start address 0x%08x not page aligned\n",
paddr);
return;
}
if (!memblock_is_region_memory(paddr, size)) {
pr_err("Illegal SDRAM region 0x%08x..0x%08x for VRAM\n",
paddr, paddr + size - 1);
return;
}
if (memblock_is_region_reserved(paddr, size)) {
pr_err("FB: failed to reserve VRAM - busy\n");
return;
}
if (memblock_reserve(paddr, size) < 0) {
pr_err("FB: failed to reserve VRAM - no memory\n");
return;
}
} else {
paddr = memblock_alloc(size, SZ_2M);
}
memblock_free(paddr, size);
memblock_remove(paddr, size);
omap_vram_add_region(paddr, size);
pr_info("Reserving %u bytes SDRAM for VRAM\n", size);
}
void __init omap_vram_set_sdram_vram(u32 size, u32 start)
{
omap_vram_sdram_start = start;
omap_vram_sdram_size = size;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册