Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
linux
Commits
5a60f4b6
Commit
5a60f4b6
authored
Jul 27, 2017
by
Jens Axboe
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'nvme-4.13' of
git://git.infradead.org/nvme
into for-linus
Pull NVMe fixes from Christoph
parents
e9193da0
7dd1ab16
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
145 additions
and
110 deletions
+145
-110
drivers/nvme/host/core.c
drivers/nvme/host/core.c
+5
-1
drivers/nvme/host/fc.c
drivers/nvme/host/fc.c
+64
-57
drivers/nvme/host/pci.c
drivers/nvme/host/pci.c
+3
-3
drivers/nvme/target/fc.c
drivers/nvme/target/fc.c
+53
-48
include/linux/nvme-fc.h
include/linux/nvme-fc.h
+19
-0
include/linux/nvme.h
include/linux/nvme.h
+1
-1
No files found.
drivers/nvme/host/core.c
View file @
5a60f4b6
...
@@ -1995,6 +1995,9 @@ static ssize_t wwid_show(struct device *dev, struct device_attribute *attr,
...
@@ -1995,6 +1995,9 @@ static ssize_t wwid_show(struct device *dev, struct device_attribute *attr,
int
serial_len
=
sizeof
(
ctrl
->
serial
);
int
serial_len
=
sizeof
(
ctrl
->
serial
);
int
model_len
=
sizeof
(
ctrl
->
model
);
int
model_len
=
sizeof
(
ctrl
->
model
);
if
(
!
uuid_is_null
(
&
ns
->
uuid
))
return
sprintf
(
buf
,
"uuid.%pU
\n
"
,
&
ns
->
uuid
);
if
(
memchr_inv
(
ns
->
nguid
,
0
,
sizeof
(
ns
->
nguid
)))
if
(
memchr_inv
(
ns
->
nguid
,
0
,
sizeof
(
ns
->
nguid
)))
return
sprintf
(
buf
,
"eui.%16phN
\n
"
,
ns
->
nguid
);
return
sprintf
(
buf
,
"eui.%16phN
\n
"
,
ns
->
nguid
);
...
@@ -2709,6 +2712,7 @@ void nvme_kill_queues(struct nvme_ctrl *ctrl)
...
@@ -2709,6 +2712,7 @@ void nvme_kill_queues(struct nvme_ctrl *ctrl)
mutex_lock
(
&
ctrl
->
namespaces_mutex
);
mutex_lock
(
&
ctrl
->
namespaces_mutex
);
/* Forcibly unquiesce queues to avoid blocking dispatch */
/* Forcibly unquiesce queues to avoid blocking dispatch */
if
(
ctrl
->
admin_q
)
blk_mq_unquiesce_queue
(
ctrl
->
admin_q
);
blk_mq_unquiesce_queue
(
ctrl
->
admin_q
);
list_for_each_entry
(
ns
,
&
ctrl
->
namespaces
,
list
)
{
list_for_each_entry
(
ns
,
&
ctrl
->
namespaces
,
list
)
{
...
...
drivers/nvme/host/fc.c
View file @
5a60f4b6
...
@@ -1888,7 +1888,7 @@ nvme_fc_start_fcp_op(struct nvme_fc_ctrl *ctrl, struct nvme_fc_queue *queue,
...
@@ -1888,7 +1888,7 @@ nvme_fc_start_fcp_op(struct nvme_fc_ctrl *ctrl, struct nvme_fc_queue *queue,
* the target device is present
* the target device is present
*/
*/
if
(
ctrl
->
rport
->
remoteport
.
port_state
!=
FC_OBJSTATE_ONLINE
)
if
(
ctrl
->
rport
->
remoteport
.
port_state
!=
FC_OBJSTATE_ONLINE
)
return
BLK_STS_IOERR
;
goto
busy
;
if
(
!
nvme_fc_ctrl_get
(
ctrl
))
if
(
!
nvme_fc_ctrl_get
(
ctrl
))
return
BLK_STS_IOERR
;
return
BLK_STS_IOERR
;
...
@@ -1958,22 +1958,25 @@ nvme_fc_start_fcp_op(struct nvme_fc_ctrl *ctrl, struct nvme_fc_queue *queue,
...
@@ -1958,22 +1958,25 @@ nvme_fc_start_fcp_op(struct nvme_fc_ctrl *ctrl, struct nvme_fc_queue *queue,
queue
->
lldd_handle
,
&
op
->
fcp_req
);
queue
->
lldd_handle
,
&
op
->
fcp_req
);
if
(
ret
)
{
if
(
ret
)
{
if
(
op
->
rq
)
/* normal request */
if
(
!
(
op
->
flags
&
FCOP_FLAGS_AEN
))
nvme_fc_unmap_data
(
ctrl
,
op
->
rq
,
op
);
nvme_fc_unmap_data
(
ctrl
,
op
->
rq
,
op
);
/* else - aen. no cleanup needed */
nvme_fc_ctrl_put
(
ctrl
);
nvme_fc_ctrl_put
(
ctrl
);
if
(
ret
!=
-
EBUSY
)
if
(
ctrl
->
rport
->
remoteport
.
port_state
==
FC_OBJSTATE_ONLINE
&&
ret
!=
-
EBUSY
)
return
BLK_STS_IOERR
;
return
BLK_STS_IOERR
;
if
(
op
->
rq
)
goto
busy
;
blk_mq_delay_run_hw_queue
(
queue
->
hctx
,
NVMEFC_QUEUE_DELAY
);
return
BLK_STS_RESOURCE
;
}
}
return
BLK_STS_OK
;
return
BLK_STS_OK
;
busy:
if
(
!
(
op
->
flags
&
FCOP_FLAGS_AEN
)
&&
queue
->
hctx
)
blk_mq_delay_run_hw_queue
(
queue
->
hctx
,
NVMEFC_QUEUE_DELAY
);
return
BLK_STS_RESOURCE
;
}
}
static
blk_status_t
static
blk_status_t
...
@@ -2802,66 +2805,70 @@ nvme_fc_init_ctrl(struct device *dev, struct nvmf_ctrl_options *opts,
...
@@ -2802,66 +2805,70 @@ nvme_fc_init_ctrl(struct device *dev, struct nvmf_ctrl_options *opts,
return
ERR_PTR
(
ret
);
return
ERR_PTR
(
ret
);
}
}
enum
{
FCT_TRADDR_ERR
=
0
,
FCT_TRADDR_WWNN
=
1
<<
0
,
FCT_TRADDR_WWPN
=
1
<<
1
,
};
struct
nvmet_fc_traddr
{
struct
nvmet_fc_traddr
{
u64
nn
;
u64
nn
;
u64
pn
;
u64
pn
;
};
};
static
const
match_table_t
traddr_opt_tokens
=
{
{
FCT_TRADDR_WWNN
,
"nn-%s"
},
{
FCT_TRADDR_WWPN
,
"pn-%s"
},
{
FCT_TRADDR_ERR
,
NULL
}
};
static
int
static
int
nvme_fc_parse_address
(
struct
nvmet_fc_traddr
*
traddr
,
char
*
buf
)
__nvme_fc_parse_u64
(
substring_t
*
sstr
,
u64
*
val
)
{
{
substring_t
args
[
MAX_OPT_ARGS
];
char
*
options
,
*
o
,
*
p
;
int
token
,
ret
=
0
;
u64
token64
;
u64
token64
;
options
=
o
=
kstrdup
(
buf
,
GFP_KERNEL
);
if
(
match_u64
(
sstr
,
&
token64
))
if
(
!
options
)
return
-
EINVAL
;
return
-
ENOMEM
;
*
val
=
token64
;
while
((
p
=
strsep
(
&
o
,
":
\n
"
))
!=
NULL
)
{
return
0
;
if
(
!*
p
)
}
continue
;
token
=
match_token
(
p
,
traddr_opt_tokens
,
args
);
/*
switch
(
token
)
{
* This routine validates and extracts the WWN's from the TRADDR string.
case
FCT_TRADDR_WWNN
:
* As kernel parsers need the 0x to determine number base, universally
if
(
match_u64
(
args
,
&
token64
))
{
* build string to parse with 0x prefix before parsing name strings.
ret
=
-
EINVAL
;
*/
goto
out
;
static
int
}
nvme_fc_parse_traddr
(
struct
nvmet_fc_traddr
*
traddr
,
char
*
buf
,
size_t
blen
)
traddr
->
nn
=
token64
;
{
break
;
char
name
[
2
+
NVME_FC_TRADDR_HEXNAMELEN
+
1
];
case
FCT_TRADDR_WWPN
:
substring_t
wwn
=
{
name
,
&
name
[
sizeof
(
name
)
-
1
]
};
if
(
match_u64
(
args
,
&
token64
))
{
int
nnoffset
,
pnoffset
;
ret
=
-
EINVAL
;
goto
out
;
/* validate it string one of the 2 allowed formats */
}
if
(
strnlen
(
buf
,
blen
)
==
NVME_FC_TRADDR_MAXLENGTH
&&
traddr
->
pn
=
token64
;
!
strncmp
(
buf
,
"nn-0x"
,
NVME_FC_TRADDR_OXNNLEN
)
&&
break
;
!
strncmp
(
&
buf
[
NVME_FC_TRADDR_MAX_PN_OFFSET
],
default:
"pn-0x"
,
NVME_FC_TRADDR_OXNNLEN
))
{
pr_warn
(
"unknown traddr token or missing value '%s'
\n
"
,
nnoffset
=
NVME_FC_TRADDR_OXNNLEN
;
p
);
pnoffset
=
NVME_FC_TRADDR_MAX_PN_OFFSET
+
ret
=
-
EINVAL
;
NVME_FC_TRADDR_OXNNLEN
;
goto
out
;
}
else
if
((
strnlen
(
buf
,
blen
)
==
NVME_FC_TRADDR_MINLENGTH
&&
}
!
strncmp
(
buf
,
"nn-"
,
NVME_FC_TRADDR_NNLEN
)
&&
}
!
strncmp
(
&
buf
[
NVME_FC_TRADDR_MIN_PN_OFFSET
],
"pn-"
,
NVME_FC_TRADDR_NNLEN
)))
{
nnoffset
=
NVME_FC_TRADDR_NNLEN
;
pnoffset
=
NVME_FC_TRADDR_MIN_PN_OFFSET
+
NVME_FC_TRADDR_NNLEN
;
}
else
goto
out_einval
;
out:
name
[
0
]
=
'0'
;
kfree
(
options
);
name
[
1
]
=
'x'
;
return
ret
;
name
[
2
+
NVME_FC_TRADDR_HEXNAMELEN
]
=
0
;
memcpy
(
&
name
[
2
],
&
buf
[
nnoffset
],
NVME_FC_TRADDR_HEXNAMELEN
);
if
(
__nvme_fc_parse_u64
(
&
wwn
,
&
traddr
->
nn
))
goto
out_einval
;
memcpy
(
&
name
[
2
],
&
buf
[
pnoffset
],
NVME_FC_TRADDR_HEXNAMELEN
);
if
(
__nvme_fc_parse_u64
(
&
wwn
,
&
traddr
->
pn
))
goto
out_einval
;
return
0
;
out_einval:
pr_warn
(
"%s: bad traddr string
\n
"
,
__func__
);
return
-
EINVAL
;
}
}
static
struct
nvme_ctrl
*
static
struct
nvme_ctrl
*
...
@@ -2875,11 +2882,11 @@ nvme_fc_create_ctrl(struct device *dev, struct nvmf_ctrl_options *opts)
...
@@ -2875,11 +2882,11 @@ nvme_fc_create_ctrl(struct device *dev, struct nvmf_ctrl_options *opts)
unsigned
long
flags
;
unsigned
long
flags
;
int
ret
;
int
ret
;
ret
=
nvme_fc_parse_
address
(
&
raddr
,
opts
->
traddr
);
ret
=
nvme_fc_parse_
traddr
(
&
raddr
,
opts
->
traddr
,
NVMF_TRADDR_SIZE
);
if
(
ret
||
!
raddr
.
nn
||
!
raddr
.
pn
)
if
(
ret
||
!
raddr
.
nn
||
!
raddr
.
pn
)
return
ERR_PTR
(
-
EINVAL
);
return
ERR_PTR
(
-
EINVAL
);
ret
=
nvme_fc_parse_
address
(
&
laddr
,
opts
->
host_traddr
);
ret
=
nvme_fc_parse_
traddr
(
&
laddr
,
opts
->
host_traddr
,
NVMF_TRADDR_SIZE
);
if
(
ret
||
!
laddr
.
nn
||
!
laddr
.
pn
)
if
(
ret
||
!
laddr
.
nn
||
!
laddr
.
pn
)
return
ERR_PTR
(
-
EINVAL
);
return
ERR_PTR
(
-
EINVAL
);
...
...
drivers/nvme/host/pci.c
View file @
5a60f4b6
...
@@ -1619,7 +1619,7 @@ static void nvme_free_host_mem(struct nvme_dev *dev)
...
@@ -1619,7 +1619,7 @@ static void nvme_free_host_mem(struct nvme_dev *dev)
static
int
nvme_alloc_host_mem
(
struct
nvme_dev
*
dev
,
u64
min
,
u64
preferred
)
static
int
nvme_alloc_host_mem
(
struct
nvme_dev
*
dev
,
u64
min
,
u64
preferred
)
{
{
struct
nvme_host_mem_buf_desc
*
descs
;
struct
nvme_host_mem_buf_desc
*
descs
;
u32
chunk_size
,
max_entries
;
u32
chunk_size
,
max_entries
,
len
;
int
i
=
0
;
int
i
=
0
;
void
**
bufs
;
void
**
bufs
;
u64
size
=
0
,
tmp
;
u64
size
=
0
,
tmp
;
...
@@ -1638,10 +1638,10 @@ static int nvme_alloc_host_mem(struct nvme_dev *dev, u64 min, u64 preferred)
...
@@ -1638,10 +1638,10 @@ static int nvme_alloc_host_mem(struct nvme_dev *dev, u64 min, u64 preferred)
if
(
!
bufs
)
if
(
!
bufs
)
goto
out_free_descs
;
goto
out_free_descs
;
for
(
size
=
0
;
size
<
preferred
;
size
+=
chunk_size
)
{
for
(
size
=
0
;
size
<
preferred
;
size
+=
len
)
{
u32
len
=
min_t
(
u64
,
chunk_size
,
preferred
-
size
);
dma_addr_t
dma_addr
;
dma_addr_t
dma_addr
;
len
=
min_t
(
u64
,
chunk_size
,
preferred
-
size
);
bufs
[
i
]
=
dma_alloc_attrs
(
dev
->
dev
,
len
,
&
dma_addr
,
GFP_KERNEL
,
bufs
[
i
]
=
dma_alloc_attrs
(
dev
->
dev
,
len
,
&
dma_addr
,
GFP_KERNEL
,
DMA_ATTR_NO_KERNEL_MAPPING
|
DMA_ATTR_NO_WARN
);
DMA_ATTR_NO_KERNEL_MAPPING
|
DMA_ATTR_NO_WARN
);
if
(
!
bufs
[
i
])
if
(
!
bufs
[
i
])
...
...
drivers/nvme/target/fc.c
View file @
5a60f4b6
...
@@ -2293,66 +2293,70 @@ nvmet_fc_rcv_fcp_abort(struct nvmet_fc_target_port *target_port,
...
@@ -2293,66 +2293,70 @@ nvmet_fc_rcv_fcp_abort(struct nvmet_fc_target_port *target_port,
}
}
EXPORT_SYMBOL_GPL
(
nvmet_fc_rcv_fcp_abort
);
EXPORT_SYMBOL_GPL
(
nvmet_fc_rcv_fcp_abort
);
enum
{
FCT_TRADDR_ERR
=
0
,
FCT_TRADDR_WWNN
=
1
<<
0
,
FCT_TRADDR_WWPN
=
1
<<
1
,
};
struct
nvmet_fc_traddr
{
struct
nvmet_fc_traddr
{
u64
nn
;
u64
nn
;
u64
pn
;
u64
pn
;
};
};
static
const
match_table_t
traddr_opt_tokens
=
{
{
FCT_TRADDR_WWNN
,
"nn-%s"
},
{
FCT_TRADDR_WWPN
,
"pn-%s"
},
{
FCT_TRADDR_ERR
,
NULL
}
};
static
int
static
int
nvmet_fc_parse_traddr
(
struct
nvmet_fc_traddr
*
traddr
,
char
*
buf
)
__nvme_fc_parse_u64
(
substring_t
*
sstr
,
u64
*
val
)
{
{
substring_t
args
[
MAX_OPT_ARGS
];
char
*
options
,
*
o
,
*
p
;
int
token
,
ret
=
0
;
u64
token64
;
u64
token64
;
options
=
o
=
kstrdup
(
buf
,
GFP_KERNEL
);
if
(
match_u64
(
sstr
,
&
token64
))
if
(
!
options
)
return
-
EINVAL
;
return
-
ENOMEM
;
*
val
=
token64
;
while
((
p
=
strsep
(
&
o
,
":
\n
"
))
!=
NULL
)
{
return
0
;
if
(
!*
p
)
}
continue
;
token
=
match_token
(
p
,
traddr_opt_tokens
,
args
);
/*
switch
(
token
)
{
* This routine validates and extracts the WWN's from the TRADDR string.
case
FCT_TRADDR_WWNN
:
* As kernel parsers need the 0x to determine number base, universally
if
(
match_u64
(
args
,
&
token64
))
{
* build string to parse with 0x prefix before parsing name strings.
ret
=
-
EINVAL
;
*/
goto
out
;
static
int
}
nvme_fc_parse_traddr
(
struct
nvmet_fc_traddr
*
traddr
,
char
*
buf
,
size_t
blen
)
traddr
->
nn
=
token64
;
{
break
;
char
name
[
2
+
NVME_FC_TRADDR_HEXNAMELEN
+
1
];
case
FCT_TRADDR_WWPN
:
substring_t
wwn
=
{
name
,
&
name
[
sizeof
(
name
)
-
1
]
};
if
(
match_u64
(
args
,
&
token64
))
{
int
nnoffset
,
pnoffset
;
ret
=
-
EINVAL
;
goto
out
;
/* validate it string one of the 2 allowed formats */
}
if
(
strnlen
(
buf
,
blen
)
==
NVME_FC_TRADDR_MAXLENGTH
&&
traddr
->
pn
=
token64
;
!
strncmp
(
buf
,
"nn-0x"
,
NVME_FC_TRADDR_OXNNLEN
)
&&
break
;
!
strncmp
(
&
buf
[
NVME_FC_TRADDR_MAX_PN_OFFSET
],
default:
"pn-0x"
,
NVME_FC_TRADDR_OXNNLEN
))
{
pr_warn
(
"unknown traddr token or missing value '%s'
\n
"
,
nnoffset
=
NVME_FC_TRADDR_OXNNLEN
;
p
);
pnoffset
=
NVME_FC_TRADDR_MAX_PN_OFFSET
+
ret
=
-
EINVAL
;
NVME_FC_TRADDR_OXNNLEN
;
goto
out
;
}
else
if
((
strnlen
(
buf
,
blen
)
==
NVME_FC_TRADDR_MINLENGTH
&&
}
!
strncmp
(
buf
,
"nn-"
,
NVME_FC_TRADDR_NNLEN
)
&&
}
!
strncmp
(
&
buf
[
NVME_FC_TRADDR_MIN_PN_OFFSET
],
"pn-"
,
NVME_FC_TRADDR_NNLEN
)))
{
nnoffset
=
NVME_FC_TRADDR_NNLEN
;
pnoffset
=
NVME_FC_TRADDR_MIN_PN_OFFSET
+
NVME_FC_TRADDR_NNLEN
;
}
else
goto
out_einval
;
out:
name
[
0
]
=
'0'
;
kfree
(
options
);
name
[
1
]
=
'x'
;
return
ret
;
name
[
2
+
NVME_FC_TRADDR_HEXNAMELEN
]
=
0
;
memcpy
(
&
name
[
2
],
&
buf
[
nnoffset
],
NVME_FC_TRADDR_HEXNAMELEN
);
if
(
__nvme_fc_parse_u64
(
&
wwn
,
&
traddr
->
nn
))
goto
out_einval
;
memcpy
(
&
name
[
2
],
&
buf
[
pnoffset
],
NVME_FC_TRADDR_HEXNAMELEN
);
if
(
__nvme_fc_parse_u64
(
&
wwn
,
&
traddr
->
pn
))
goto
out_einval
;
return
0
;
out_einval:
pr_warn
(
"%s: bad traddr string
\n
"
,
__func__
);
return
-
EINVAL
;
}
}
static
int
static
int
...
@@ -2370,7 +2374,8 @@ nvmet_fc_add_port(struct nvmet_port *port)
...
@@ -2370,7 +2374,8 @@ nvmet_fc_add_port(struct nvmet_port *port)
/* map the traddr address info to a target port */
/* map the traddr address info to a target port */
ret
=
nvmet_fc_parse_traddr
(
&
traddr
,
port
->
disc_addr
.
traddr
);
ret
=
nvme_fc_parse_traddr
(
&
traddr
,
port
->
disc_addr
.
traddr
,
sizeof
(
port
->
disc_addr
.
traddr
));
if
(
ret
)
if
(
ret
)
return
ret
;
return
ret
;
...
...
include/linux/nvme-fc.h
View file @
5a60f4b6
...
@@ -334,5 +334,24 @@ struct fcnvme_ls_disconnect_acc {
...
@@ -334,5 +334,24 @@ struct fcnvme_ls_disconnect_acc {
#define NVME_FC_LS_TIMEOUT_SEC 2
/* 2 seconds */
#define NVME_FC_LS_TIMEOUT_SEC 2
/* 2 seconds */
#define NVME_FC_TGTOP_TIMEOUT_SEC 2
/* 2 seconds */
#define NVME_FC_TGTOP_TIMEOUT_SEC 2
/* 2 seconds */
/*
* TRADDR string must be of form "nn-<16hexdigits>:pn-<16hexdigits>"
* the string is allowed to be specified with or without a "0x" prefix
* infront of the <16hexdigits>. Without is considered the "min" string
* and with is considered the "max" string. The hexdigits may be upper
* or lower case.
*/
#define NVME_FC_TRADDR_NNLEN 3
/* "?n-" */
#define NVME_FC_TRADDR_OXNNLEN 5
/* "?n-0x" */
#define NVME_FC_TRADDR_HEXNAMELEN 16
#define NVME_FC_TRADDR_MINLENGTH \
(2 * (NVME_FC_TRADDR_NNLEN + NVME_FC_TRADDR_HEXNAMELEN) + 1)
#define NVME_FC_TRADDR_MAXLENGTH \
(2 * (NVME_FC_TRADDR_OXNNLEN + NVME_FC_TRADDR_HEXNAMELEN) + 1)
#define NVME_FC_TRADDR_MIN_PN_OFFSET \
(NVME_FC_TRADDR_NNLEN + NVME_FC_TRADDR_HEXNAMELEN + 1)
#define NVME_FC_TRADDR_MAX_PN_OFFSET \
(NVME_FC_TRADDR_OXNNLEN + NVME_FC_TRADDR_HEXNAMELEN + 1)
#endif
/* _NVME_FC_H */
#endif
/* _NVME_FC_H */
include/linux/nvme.h
View file @
5a60f4b6
...
@@ -1006,7 +1006,7 @@ static inline bool nvme_is_write(struct nvme_command *cmd)
...
@@ -1006,7 +1006,7 @@ static inline bool nvme_is_write(struct nvme_command *cmd)
* Why can't we simply have a Fabrics In and Fabrics out command?
* Why can't we simply have a Fabrics In and Fabrics out command?
*/
*/
if
(
unlikely
(
cmd
->
common
.
opcode
==
nvme_fabrics_command
))
if
(
unlikely
(
cmd
->
common
.
opcode
==
nvme_fabrics_command
))
return
cmd
->
fabrics
.
opcod
e
&
1
;
return
cmd
->
fabrics
.
fctyp
e
&
1
;
return
cmd
->
common
.
opcode
&
1
;
return
cmd
->
common
.
opcode
&
1
;
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment