提交 89aa19d0 编写于 作者: T Tom Rini

Merge tag 'fpga-for-v2018.11' of git://git.denx.de/u-boot-microblaze

FPGA changes for v2018.11

- add fpga tests to cover fpga commands
- fpga Kconfig cleanup
- fix cmd/fpga.c
- add support for missing fpga loadmk commands
- add fpga fragment to MAINTAINERS
......@@ -391,6 +391,14 @@ F: test/py/tests/test_efi*
F: cmd/bootefi.c
F: tools/file2include.c
FPGA
M: Michal Simek <michal.simek@xilinx.com>
S: Maintained
T: git git://git.denx.de/u-boot-microblaze.git
F: drivers/fpga/
F: cmd/fpga.c
F: include/fpga.h
FLATTENED DEVICE TREE
M: Simon Glass <sjg@chromium.org>
S: Maintained
......
......@@ -13,388 +13,408 @@
#include <fs.h>
#include <malloc.h>
/* Local functions */
static int fpga_get_op(char *opstr);
/* Local defines */
enum {
FPGA_NONE = -1,
FPGA_INFO,
FPGA_LOAD,
FPGA_LOADB,
FPGA_DUMP,
FPGA_LOADMK,
FPGA_LOADP,
FPGA_LOADBP,
FPGA_LOADFS,
FPGA_LOADS,
};
/* ------------------------------------------------------------------------- */
/* command form:
* fpga <op> <device number> <data addr> <datasize>
* where op is 'load', 'dump', or 'info'
* If there is no device number field, the fpga environment variable is used.
* If there is no data addr field, the fpgadata environment variable is used.
* The info command requires no data address field.
*/
int do_fpga(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
static long do_fpga_get_device(char *arg)
{
int op, dev = FPGA_INVALID_DEVICE;
size_t data_size = 0;
void *fpga_data = NULL;
long dev = FPGA_INVALID_DEVICE;
char *devstr = env_get("fpga");
char *datastr = env_get("fpgadata");
int rc = FPGA_FAIL;
int wrong_parms = 0;
#if defined(CONFIG_FIT)
const char *fit_uname = NULL;
ulong fit_addr;
#endif
#if defined(CONFIG_CMD_FPGA_LOADFS)
fpga_fs_info fpga_fsinfo;
fpga_fsinfo.fstype = FS_TYPE_ANY;
#endif
#if defined(CONFIG_CMD_FPGA_LOAD_SECURE)
struct fpga_secure_info fpga_sec_info;
memset(&fpga_sec_info, 0, sizeof(fpga_sec_info));
#endif
if (devstr)
dev = (int) simple_strtoul(devstr, NULL, 16);
if (datastr)
fpga_data = (void *)simple_strtoul(datastr, NULL, 16);
/* Should be strtol to handle -1 cases */
dev = simple_strtol(devstr, NULL, 16);
if (dev == FPGA_INVALID_DEVICE && arg)
dev = simple_strtol(arg, NULL, 16);
if (argc > 9 || argc < 2) {
debug("%s: Too many or too few args (%d)\n", __func__, argc);
debug("%s: device = %ld\n", __func__, dev);
return dev;
}
static int do_fpga_check_params(long *dev, long *fpga_data, size_t *data_size,
cmd_tbl_t *cmdtp, int argc, char *const argv[])
{
size_t local_data_size;
long local_fpga_data;
debug("%s %d, %d\n", __func__, argc, cmdtp->maxargs);
if (argc != cmdtp->maxargs) {
debug("fpga: incorrect parameters passed\n");
return CMD_RET_USAGE;
}
op = (int)fpga_get_op(argv[1]);
*dev = do_fpga_get_device(argv[0]);
switch (op) {
#if defined(CONFIG_CMD_FPGA_LOADFS)
case FPGA_LOADFS:
if (argc < 9)
return CMD_RET_USAGE;
fpga_fsinfo.blocksize = (unsigned int)
simple_strtoul(argv[5], NULL, 16);
fpga_fsinfo.interface = argv[6];
fpga_fsinfo.dev_part = argv[7];
fpga_fsinfo.filename = argv[8];
argc = 5;
break;
#endif
#if defined(CONFIG_CMD_FPGA_LOAD_SECURE)
case FPGA_LOADS:
if (argc < 7)
return CMD_RET_USAGE;
if (argc == 8)
fpga_sec_info.userkey_addr = (u8 *)(uintptr_t)
simple_strtoull(argv[7],
NULL, 16);
fpga_sec_info.encflag = (u8)simple_strtoul(argv[6], NULL, 16);
fpga_sec_info.authflag = (u8)simple_strtoul(argv[5], NULL, 16);
argc = 5;
break;
#endif
default:
break;
local_fpga_data = simple_strtol(argv[1], NULL, 16);
if (!local_fpga_data) {
debug("fpga: zero fpga_data address\n");
return CMD_RET_USAGE;
}
*fpga_data = local_fpga_data;
switch (argc) {
case 5: /* fpga <op> <dev> <data> <datasize> */
data_size = simple_strtoul(argv[4], NULL, 16);
local_data_size = simple_strtoul(argv[2], NULL, 16);
if (!local_data_size) {
debug("fpga: zero size\n");
return CMD_RET_USAGE;
}
*data_size = local_data_size;
case 4: /* fpga <op> <dev> <data> */
#if defined(CONFIG_FIT)
if (fit_parse_subimage(argv[3], (ulong)fpga_data,
&fit_addr, &fit_uname)) {
fpga_data = (void *)fit_addr;
debug("* fpga: subimage '%s' from FIT image ",
fit_uname);
debug("at 0x%08lx\n", fit_addr);
} else
#endif
{
fpga_data = (void *)simple_strtoul(argv[3], NULL, 16);
debug("* fpga: cmdline image address = 0x%08lx\n",
(ulong)fpga_data);
}
debug("%s: fpga_data = 0x%lx\n", __func__, (ulong)fpga_data);
return 0;
}
#if defined(CONFIG_CMD_FPGA_LOAD_SECURE)
int do_fpga_loads(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
size_t data_size = 0;
long fpga_data, dev;
int ret;
struct fpga_secure_info fpga_sec_info;
case 3: /* fpga <op> <dev | data addr> */
dev = (int)simple_strtoul(argv[2], NULL, 16);
debug("%s: device = %d\n", __func__, dev);
/* FIXME - this is a really weak test */
if ((argc == 3) && (dev > fpga_count())) {
/* must be buffer ptr */
debug("%s: Assuming buffer pointer in arg 3\n",
__func__);
memset(&fpga_sec_info, 0, sizeof(fpga_sec_info));
#if defined(CONFIG_FIT)
if (fit_parse_subimage(argv[2], (ulong)fpga_data,
&fit_addr, &fit_uname)) {
fpga_data = (void *)fit_addr;
debug("* fpga: subimage '%s' from FIT image ",
fit_uname);
debug("at 0x%08lx\n", fit_addr);
} else
#endif
{
fpga_data = (void *)(uintptr_t)dev;
debug("* fpga: cmdline image addr = 0x%08lx\n",
(ulong)fpga_data);
}
if (argc < 5) {
debug("fpga: incorrect parameters passed\n");
return CMD_RET_USAGE;
}
debug("%s: fpga_data = 0x%lx\n",
__func__, (ulong)fpga_data);
dev = FPGA_INVALID_DEVICE; /* reset device num */
}
if (argc == 6)
fpga_sec_info.userkey_addr = (u8 *)(uintptr_t)
simple_strtoull(argv[5],
NULL, 16);
else
/*
* If 6th parameter is not passed then do_fpga_check_params
* will get 5 instead of expected 6 which means that function
* return CMD_RET_USAGE. Increase number of params +1 to pass
* this.
*/
argc++;
fpga_sec_info.encflag = (u8)simple_strtoul(argv[4], NULL, 16);
fpga_sec_info.authflag = (u8)simple_strtoul(argv[3], NULL, 16);
if (fpga_sec_info.authflag >= FPGA_NO_ENC_OR_NO_AUTH &&
fpga_sec_info.encflag >= FPGA_NO_ENC_OR_NO_AUTH) {
debug("fpga: Use <fpga load> for NonSecure bitstream\n");
return CMD_RET_USAGE;
}
if (dev == FPGA_INVALID_DEVICE) {
puts("FPGA device not specified\n");
op = FPGA_NONE;
if (fpga_sec_info.encflag == FPGA_ENC_USR_KEY &&
!fpga_sec_info.userkey_addr) {
debug("fpga: User key not provided\n");
return CMD_RET_USAGE;
}
switch (op) {
case FPGA_NONE:
case FPGA_INFO:
break;
#if defined(CONFIG_CMD_FPGA_LOADFS)
case FPGA_LOADFS:
/* Blocksize can be zero */
if (!fpga_fsinfo.interface || !fpga_fsinfo.dev_part ||
!fpga_fsinfo.filename)
wrong_parms = 1;
break;
#endif
#if defined(CONFIG_CMD_FPGA_LOAD_SECURE)
case FPGA_LOADS:
if (fpga_sec_info.authflag >= FPGA_NO_ENC_OR_NO_AUTH &&
fpga_sec_info.encflag >= FPGA_NO_ENC_OR_NO_AUTH) {
puts("ERR: use <fpga load> for NonSecure bitstream\n");
wrong_parms = 1;
}
ret = do_fpga_check_params(&dev, &fpga_data, &data_size,
cmdtp, argc, argv);
if (ret)
return ret;
if (fpga_sec_info.encflag == FPGA_ENC_USR_KEY &&
!fpga_sec_info.userkey_addr) {
wrong_parms = 1;
puts("ERR:User key not provided\n");
}
break;
return fpga_loads(dev, (void *)fpga_data, data_size, &fpga_sec_info);
}
#endif
case FPGA_LOAD:
case FPGA_LOADP:
case FPGA_LOADB:
case FPGA_LOADBP:
case FPGA_DUMP:
if (!fpga_data || !data_size)
wrong_parms = 1;
break;
#if defined(CONFIG_CMD_FPGA_LOADMK)
case FPGA_LOADMK:
if (!fpga_data)
wrong_parms = 1;
break;
#if defined(CONFIG_CMD_FPGA_LOADFS)
static int do_fpga_loadfs(cmd_tbl_t *cmdtp, int flag, int argc,
char *const argv[])
{
size_t data_size = 0;
long fpga_data, dev;
int ret;
fpga_fs_info fpga_fsinfo;
ret = do_fpga_check_params(&dev, &fpga_data, &data_size,
cmdtp, argc, argv);
if (ret)
return ret;
fpga_fsinfo.fstype = FS_TYPE_ANY;
fpga_fsinfo.blocksize = (unsigned int)simple_strtoul(argv[3], NULL, 16);
fpga_fsinfo.interface = argv[4];
fpga_fsinfo.dev_part = argv[5];
fpga_fsinfo.filename = argv[6];
return fpga_fsload(dev, (void *)fpga_data, data_size, &fpga_fsinfo);
}
#endif
}
if (wrong_parms) {
puts("Wrong parameters for FPGA request\n");
op = FPGA_NONE;
}
static int do_fpga_info(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
long dev = do_fpga_get_device(argv[0]);
switch (op) {
case FPGA_NONE:
return CMD_RET_USAGE;
return fpga_info(dev);
}
static int do_fpga_dump(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
size_t data_size = 0;
long fpga_data, dev;
int ret;
ret = do_fpga_check_params(&dev, &fpga_data, &data_size,
cmdtp, argc, argv);
if (ret)
return ret;
return fpga_dump(dev, (void *)fpga_data, data_size);
}
case FPGA_INFO:
rc = fpga_info(dev);
break;
static int do_fpga_load(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
size_t data_size = 0;
long fpga_data, dev;
int ret;
ret = do_fpga_check_params(&dev, &fpga_data, &data_size,
cmdtp, argc, argv);
if (ret)
return ret;
case FPGA_LOAD:
rc = fpga_load(dev, fpga_data, data_size, BIT_FULL);
break;
return fpga_load(dev, (void *)fpga_data, data_size, BIT_FULL);
}
static int do_fpga_loadb(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
size_t data_size = 0;
long fpga_data, dev;
int ret;
ret = do_fpga_check_params(&dev, &fpga_data, &data_size,
cmdtp, argc, argv);
if (ret)
return ret;
return fpga_loadbitstream(dev, (void *)fpga_data, data_size, BIT_FULL);
}
#if defined(CONFIG_CMD_FPGA_LOADP)
case FPGA_LOADP:
rc = fpga_load(dev, fpga_data, data_size, BIT_PARTIAL);
break;
#endif
static int do_fpga_loadp(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
size_t data_size = 0;
long fpga_data, dev;
int ret;
ret = do_fpga_check_params(&dev, &fpga_data, &data_size,
cmdtp, argc, argv);
if (ret)
return ret;
case FPGA_LOADB:
rc = fpga_loadbitstream(dev, fpga_data, data_size, BIT_FULL);
break;
return fpga_load(dev, (void *)fpga_data, data_size, BIT_PARTIAL);
}
#endif
#if defined(CONFIG_CMD_FPGA_LOADBP)
case FPGA_LOADBP:
rc = fpga_loadbitstream(dev, fpga_data, data_size, BIT_PARTIAL);
break;
static int do_fpga_loadbp(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
size_t data_size = 0;
long fpga_data, dev;
int ret;
ret = do_fpga_check_params(&dev, &fpga_data, &data_size,
cmdtp, argc, argv);
if (ret)
return ret;
return fpga_loadbitstream(dev, (void *)fpga_data, data_size,
BIT_PARTIAL);
}
#endif
#if defined(CONFIG_CMD_FPGA_LOADFS)
case FPGA_LOADFS:
rc = fpga_fsload(dev, fpga_data, data_size, &fpga_fsinfo);
break;
#if defined(CONFIG_CMD_FPGA_LOADMK)
static int do_fpga_loadmk(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
size_t data_size = 0;
void *fpga_data = NULL;
#if defined(CONFIG_FIT)
const char *fit_uname = NULL;
ulong fit_addr;
#endif
ulong dev = do_fpga_get_device(argv[0]);
char *datastr = env_get("fpgadata");
#if defined(CONFIG_CMD_FPGA_LOAD_SECURE)
case FPGA_LOADS:
rc = fpga_loads(dev, fpga_data, data_size, &fpga_sec_info);
break;
debug("fpga: argc %x, dev %lx, datastr %s\n", argc, dev, datastr);
if (dev == FPGA_INVALID_DEVICE) {
debug("fpga: Invalid fpga device\n");
return CMD_RET_USAGE;
}
if (argc == 0 && !datastr) {
debug("fpga: No datastr passed\n");
return CMD_RET_USAGE;
}
if (argc == 2) {
datastr = argv[1];
debug("fpga: Full command with two args\n");
} else if (argc == 1 && !datastr) {
debug("fpga: Dev is setup - fpgadata passed\n");
datastr = argv[0];
}
#if defined(CONFIG_FIT)
if (fit_parse_subimage(datastr, (ulong)fpga_data,
&fit_addr, &fit_uname)) {
fpga_data = (void *)fit_addr;
debug("* fpga: subimage '%s' from FIT image ",
fit_uname);
debug("at 0x%08lx\n", fit_addr);
} else
#endif
{
fpga_data = (void *)simple_strtoul(datastr, NULL, 16);
debug("* fpga: cmdline image address = 0x%08lx\n",
(ulong)fpga_data);
}
debug("%s: fpga_data = 0x%lx\n", __func__, (ulong)fpga_data);
if (!fpga_data) {
puts("Zero fpga_data address\n");
return CMD_RET_USAGE;
}
#if defined(CONFIG_CMD_FPGA_LOADMK)
case FPGA_LOADMK:
switch (genimg_get_format(fpga_data)) {
switch (genimg_get_format(fpga_data)) {
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
case IMAGE_FORMAT_LEGACY:
{
image_header_t *hdr =
(image_header_t *)fpga_data;
ulong data;
uint8_t comp;
comp = image_get_comp(hdr);
if (comp == IH_COMP_GZIP) {
case IMAGE_FORMAT_LEGACY:
{
image_header_t *hdr = (image_header_t *)fpga_data;
ulong data;
u8 comp;
comp = image_get_comp(hdr);
if (comp == IH_COMP_GZIP) {
#if defined(CONFIG_GZIP)
ulong image_buf = image_get_data(hdr);
data = image_get_load(hdr);
ulong image_size = ~0UL;
if (gunzip((void *)data, ~0UL,
(void *)image_buf,
&image_size) != 0) {
puts("GUNZIP: error\n");
return 1;
}
data_size = image_size;
ulong image_buf = image_get_data(hdr);
ulong image_size = ~0UL;
data = image_get_load(hdr);
if (gunzip((void *)data, ~0UL, (void *)image_buf,
&image_size) != 0) {
puts("GUNZIP: error\n");
return CMD_RET_FAILURE;
}
data_size = image_size;
#else
puts("Gunzip image is not supported\n");
return 1;
puts("Gunzip image is not supported\n");
return 1;
#endif
} else {
data = (ulong)image_get_data(hdr);
data_size = image_get_data_size(hdr);
}
rc = fpga_load(dev, (void *)data, data_size,
BIT_FULL);
}
break;
} else {
data = (ulong)image_get_data(hdr);
data_size = image_get_data_size(hdr);
}
return fpga_load(dev, (void *)data, data_size,
BIT_FULL);
}
#endif
#if defined(CONFIG_FIT)
case IMAGE_FORMAT_FIT:
{
const void *fit_hdr = (const void *)fpga_data;
int noffset;
const void *fit_data;
if (fit_uname == NULL) {
puts("No FIT subimage unit name\n");
return 1;
}
if (!fit_check_format(fit_hdr)) {
puts("Bad FIT image format\n");
return 1;
}
/* get fpga component image node offset */
noffset = fit_image_get_node(fit_hdr,
fit_uname);
if (noffset < 0) {
printf("Can't find '%s' FIT subimage\n",
fit_uname);
return 1;
}
/* verify integrity */
if (!fit_image_verify(fit_hdr, noffset)) {
puts ("Bad Data Hash\n");
return 1;
}
/* get fpga subimage data address and length */
if (fit_image_get_data(fit_hdr, noffset,
&fit_data, &data_size)) {
puts("Fpga subimage data not found\n");
return 1;
}
rc = fpga_load(dev, fit_data, data_size,
BIT_FULL);
}
break;
#endif
default:
puts("** Unknown image type\n");
rc = FPGA_FAIL;
break;
case IMAGE_FORMAT_FIT:
{
const void *fit_hdr = (const void *)fpga_data;
int noffset;
const void *fit_data;
if (!fit_uname) {
puts("No FIT subimage unit name\n");
return CMD_RET_FAILURE;
}
if (!fit_check_format(fit_hdr)) {
puts("Bad FIT image format\n");
return CMD_RET_FAILURE;
}
break;
#endif
case FPGA_DUMP:
rc = fpga_dump(dev, fpga_data, data_size);
break;
/* get fpga component image node offset */
noffset = fit_image_get_node(fit_hdr, fit_uname);
if (noffset < 0) {
printf("Can't find '%s' FIT subimage\n", fit_uname);
return CMD_RET_FAILURE;
}
/* verify integrity */
if (!fit_image_verify(fit_hdr, noffset)) {
puts("Bad Data Hash\n");
return CMD_RET_FAILURE;
}
/* get fpga subimage data address and length */
if (fit_image_get_data(fit_hdr, noffset, &fit_data,
&data_size)) {
puts("Fpga subimage data not found\n");
return CMD_RET_FAILURE;
}
return fpga_load(dev, fit_data, data_size, BIT_FULL);
}
#endif
default:
printf("Unknown operation\n");
return CMD_RET_USAGE;
puts("** Unknown image type\n");
return CMD_RET_FAILURE;
}
return rc;
}
#endif
/*
* Map op to supported operations. We don't use a table since we
* would just have to relocate it from flash anyway.
*/
static int fpga_get_op(char *opstr)
{
int op = FPGA_NONE;
if (!strcmp("info", opstr))
op = FPGA_INFO;
else if (!strcmp("loadb", opstr))
op = FPGA_LOADB;
else if (!strcmp("load", opstr))
op = FPGA_LOAD;
static cmd_tbl_t fpga_commands[] = {
U_BOOT_CMD_MKENT(info, 1, 1, do_fpga_info, "", ""),
U_BOOT_CMD_MKENT(dump, 3, 1, do_fpga_dump, "", ""),
U_BOOT_CMD_MKENT(load, 3, 1, do_fpga_load, "", ""),
U_BOOT_CMD_MKENT(loadb, 3, 1, do_fpga_loadb, "", ""),
#if defined(CONFIG_CMD_FPGA_LOADP)
else if (!strcmp("loadp", opstr))
op = FPGA_LOADP;
U_BOOT_CMD_MKENT(loadp, 3, 1, do_fpga_loadp, "", ""),
#endif
#if defined(CONFIG_CMD_FPGA_LOADBP)
else if (!strcmp("loadbp", opstr))
op = FPGA_LOADBP;
U_BOOT_CMD_MKENT(loadbp, 3, 1, do_fpga_loadbp, "", ""),
#endif
#if defined(CONFIG_CMD_FPGA_LOADFS)
else if (!strcmp("loadfs", opstr))
op = FPGA_LOADFS;
U_BOOT_CMD_MKENT(loadfs, 7, 1, do_fpga_loadfs, "", ""),
#endif
#if defined(CONFIG_CMD_FPGA_LOADMK)
else if (!strcmp("loadmk", opstr))
op = FPGA_LOADMK;
U_BOOT_CMD_MKENT(loadmk, 2, 1, do_fpga_loadmk, "", ""),
#endif
else if (!strcmp("dump", opstr))
op = FPGA_DUMP;
#if defined(CONFIG_CMD_FPGA_LOAD_SECURE)
else if (!strcmp("loads", opstr))
op = FPGA_LOADS;
U_BOOT_CMD_MKENT(loads, 6, 1, do_fpga_loads, "", ""),
#endif
};
static int do_fpga_wrapper(cmd_tbl_t *cmdtp, int flag, int argc,
char *const argv[])
{
cmd_tbl_t *fpga_cmd;
int ret;
if (argc < 2)
return CMD_RET_USAGE;
fpga_cmd = find_cmd_tbl(argv[1], fpga_commands,
ARRAY_SIZE(fpga_commands));
if (!fpga_cmd) {
debug("fpga: non existing command\n");
return CMD_RET_USAGE;
}
argc -= 2;
argv += 2;
if (argc > fpga_cmd->maxargs) {
debug("fpga: more parameters passed\n");
return CMD_RET_USAGE;
}
if (op == FPGA_NONE)
printf("Unknown fpga operation \"%s\"\n", opstr);
ret = fpga_cmd->cmd(fpga_cmd, flag, argc, argv);
return op;
return cmd_process_error(fpga_cmd, ret);
}
#if defined(CONFIG_CMD_FPGA_LOADFS) || defined(CONFIG_CMD_FPGA_LOAD_SECURE)
U_BOOT_CMD(fpga, 9, 1, do_fpga,
U_BOOT_CMD(fpga, 9, 1, do_fpga_wrapper,
#else
U_BOOT_CMD(fpga, 6, 1, do_fpga,
U_BOOT_CMD(fpga, 6, 1, do_fpga_wrapper,
#endif
"loadable FPGA image support",
"[operation type] [device number] [image address] [image size]\n"
......
......@@ -46,15 +46,15 @@ config FPGA_ZYNQMPPL
on Xilinx Zynq UltraScale+ (ZynqMP) device.
config FPGA_SPARTAN3
bool "Enable Spartan3 FPGA driver"
help
Enable Spartan3 FPGA driver for loading in BIT format.
bool "Enable Spartan3 FPGA driver"
help
Enable Spartan3 FPGA driver for loading in BIT format.
config FPGA_ZYNQPL
bool "Enable Xilinx FPGA for Zynq"
depends on ARCH_ZYNQ
help
Enable FPGA driver for loading bitstream in BIT and BIN format
on Xilinx Zynq devices.
bool "Enable Xilinx FPGA for Zynq"
depends on ARCH_ZYNQ
help
Enable FPGA driver for loading bitstream in BIT and BIN format
on Xilinx Zynq devices.
endmenu
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册