Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
S
Simple VME FMC Carrier SVEC
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
14
Issues
14
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
image/svg+xml
Discourse
Discourse
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Projects
Simple VME FMC Carrier SVEC
Commits
18a8bc29
Commit
18a8bc29
authored
Oct 20, 2017
by
Tristan Gingold
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
vmecore_test: increase size of pattern area.
parent
c829896c
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
220 additions
and
154 deletions
+220
-154
vmecore_test.vhd
hdl/top/vmecore_test/vmecore_test.vhd
+105
-90
test_vme.c
software/vmecore_test/test_vme.c
+115
-64
No files found.
hdl/top/vmecore_test/vmecore_test.vhd
View file @
18a8bc29
...
...
@@ -59,10 +59,11 @@ architecture rtl of vmecore_test is
-- 0x1005: generates bus error.
-- 0x2000: counter (4B). Generate an interrupt when 0 is reached.
-- 0x3000: pattern ram (0x1000 * 4B)
-- 0x4000 - 0x3ff000: pattern ram
signal
counter
:
unsigned
(
31
downto
0
);
signal
leds
:
std_logic_vector
(
15
downto
0
);
signal
last_trans
:
std_logic_vector
(
2
0
downto
0
);
signal
last_trans
:
std_logic_vector
(
2
8
downto
0
);
signal
nbr_read
:
unsigned
(
15
downto
0
);
signal
nbr_write
:
unsigned
(
15
downto
0
);
...
...
@@ -79,8 +80,25 @@ begin
pattern
(
15
downto
0
)
<=
slave_i
.
adr
(
15
downto
0
);
process
(
clk_sys_i
)
procedure
pattern_write
is
variable
err
:
boolean
;
begin
err
:
=
false
;
for
i
in
3
downto
0
loop
if
slave_i
.
sel
(
i
)
=
'1'
and
(
slave_i
.
dat
(
8
*
i
+
7
downto
8
*
i
)
/=
pattern
(
8
*
i
+
7
downto
8
*
i
))
then
err
:
=
true
;
end
if
;
end
loop
;
if
err
then
nbr_write_errors
<=
nbr_write_errors
+
1
;
end
if
;
end
pattern_write
;
variable
idx
:
natural
;
variable
err
:
boolean
;
begin
if
rising_edge
(
clk_sys_i
)
then
slave_o
.
ack
<=
'0'
;
...
...
@@ -102,103 +120,100 @@ begin
if
slave_i
.
stb
=
'1'
then
if
slave_i
.
adr
(
13
downto
12
)
=
"00"
then
-- Save transaction (very cheap scope).
last_trans
(
15
downto
0
)
<=
slave_i
.
adr
(
15
downto
0
);
last_trans
(
19
downto
16
)
<=
slave_i
.
sel
;
last_trans
(
2
0
)
<=
slave_i
.
we
;
last_trans
(
23
downto
0
)
<=
slave_i
.
adr
(
23
downto
0
);
last_trans
(
27
downto
24
)
<=
slave_i
.
sel
;
last_trans
(
2
8
)
<=
slave_i
.
we
;
end
if
;
if
slave_i
.
we
=
'1'
then
-- Write
nbr_write
<=
nbr_write
+
1
;
case
slave_i
.
adr
(
13
downto
12
)
is
when
"00"
=>
idx
:
=
to_integer
(
unsigned
(
slave_i
.
adr
(
8
downto
0
)));
for
i
in
3
downto
0
loop
if
slave_i
.
sel
(
i
)
=
'1'
then
sram
(
idx
)(
8
*
i
+
7
downto
8
*
i
)
<=
slave_i
.
dat
(
8
*
i
+
7
downto
8
*
i
);
end
if
;
end
loop
;
when
"01"
=>
case
slave_i
.
adr
(
2
downto
0
)
is
when
"000"
=>
for
i
in
1
downto
0
loop
if
slave_i
.
sel
(
i
)
=
'1'
then
leds
(
8
*
i
+
7
downto
8
*
i
)
<=
slave_i
.
dat
(
8
*
i
+
7
downto
8
*
i
);
end
if
;
end
loop
;
when
"001"
=>
null
;
when
"010"
=>
nbr_read
<=
(
others
=>
'0'
);
when
"011"
=>
nbr_write
<=
(
others
=>
'0'
);
when
"100"
=>
nbr_write_errors
<=
(
others
=>
'0'
);
when
"101"
=>
slave_o
.
err
<=
'1'
;
when
others
=>
null
;
end
case
;
when
"10"
=>
for
i
in
3
downto
0
loop
if
slave_i
.
sel
(
i
)
=
'1'
then
counter
(
8
*
i
+
7
downto
8
*
i
)
<=
unsigned
(
slave_i
.
dat
(
8
*
i
+
7
downto
8
*
i
));
end
if
;
end
loop
;
when
"11"
=>
err
:
=
false
;
for
i
in
3
downto
0
loop
if
slave_i
.
sel
(
i
)
=
'1'
and
(
slave_i
.
dat
(
8
*
i
+
7
downto
8
*
i
)
/=
pattern
(
8
*
i
+
7
downto
8
*
i
))
then
err
:
=
true
;
end
if
;
end
loop
;
if
err
then
nbr_write_errors
<=
nbr_write_errors
+
1
;
end
if
;
when
others
=>
null
;
end
case
;
if
slave_i
.
adr
(
25
downto
14
)
=
x"000"
then
case
slave_i
.
adr
(
13
downto
12
)
is
when
"00"
=>
idx
:
=
to_integer
(
unsigned
(
slave_i
.
adr
(
8
downto
0
)));
for
i
in
3
downto
0
loop
if
slave_i
.
sel
(
i
)
=
'1'
then
sram
(
idx
)(
8
*
i
+
7
downto
8
*
i
)
<=
slave_i
.
dat
(
8
*
i
+
7
downto
8
*
i
);
end
if
;
end
loop
;
when
"01"
=>
case
slave_i
.
adr
(
2
downto
0
)
is
when
"000"
=>
for
i
in
1
downto
0
loop
if
slave_i
.
sel
(
i
)
=
'1'
then
leds
(
8
*
i
+
7
downto
8
*
i
)
<=
slave_i
.
dat
(
8
*
i
+
7
downto
8
*
i
);
end
if
;
end
loop
;
when
"001"
=>
null
;
when
"010"
=>
nbr_read
<=
(
others
=>
'0'
);
when
"011"
=>
nbr_write
<=
(
others
=>
'0'
);
when
"100"
=>
nbr_write_errors
<=
(
others
=>
'0'
);
when
"101"
=>
slave_o
.
err
<=
'1'
;
when
others
=>
null
;
end
case
;
when
"10"
=>
for
i
in
3
downto
0
loop
if
slave_i
.
sel
(
i
)
=
'1'
then
counter
(
8
*
i
+
7
downto
8
*
i
)
<=
unsigned
(
slave_i
.
dat
(
8
*
i
+
7
downto
8
*
i
));
end
if
;
end
loop
;
when
"11"
=>
pattern_write
;
when
others
=>
null
;
end
case
;
else
pattern_write
;
end
if
;
slave_o
.
ack
<=
'1'
;
else
-- Read
nbr_read
<=
nbr_read
+
1
;
case
slave_i
.
adr
(
13
downto
12
)
is
when
"00"
=>
idx
:
=
to_integer
(
unsigned
(
slave_i
.
adr
(
8
downto
0
)));
slave_o
.
dat
<=
sram
(
idx
);
when
"01"
=>
case
slave_i
.
adr
(
2
downto
0
)
is
when
"000"
=>
slave_o
.
dat
(
31
downto
16
)
<=
(
others
=>
'0'
);
slave_o
.
dat
(
15
downto
0
)
<=
leds
;
when
"001"
=>
slave_o
.
dat
<=
(
31
downto
21
=>
'0'
)
&
last_trans
;
when
"010"
=>
slave_o
.
dat
(
31
downto
16
)
<=
(
others
=>
'0'
);
slave_o
.
dat
(
15
downto
0
)
<=
std_logic_vector
(
nbr_read
);
when
"011"
=>
slave_o
.
dat
(
31
downto
16
)
<=
(
others
=>
'0'
);
slave_o
.
dat
(
15
downto
0
)
<=
std_logic_vector
(
nbr_write
);
when
"100"
=>
slave_o
.
dat
<=
std_logic_vector
(
nbr_write_errors
);
when
"101"
=>
slave_o
.
err
<=
'1'
;
when
others
=>
null
;
end
case
;
when
"10"
=>
slave_o
.
dat
<=
std_logic_vector
(
counter
);
when
"11"
=>
slave_o
.
dat
<=
pattern
;
when
others
=>
null
;
end
case
;
if
slave_i
.
adr
(
25
downto
14
)
=
x"000"
then
case
slave_i
.
adr
(
13
downto
12
)
is
when
"00"
=>
idx
:
=
to_integer
(
unsigned
(
slave_i
.
adr
(
8
downto
0
)));
slave_o
.
dat
<=
sram
(
idx
);
when
"01"
=>
case
slave_i
.
adr
(
2
downto
0
)
is
when
"000"
=>
slave_o
.
dat
(
31
downto
16
)
<=
(
others
=>
'0'
);
slave_o
.
dat
(
15
downto
0
)
<=
leds
;
when
"001"
=>
slave_o
.
dat
<=
(
31
downto
29
=>
'0'
)
&
last_trans
;
when
"010"
=>
slave_o
.
dat
(
31
downto
16
)
<=
(
others
=>
'0'
);
slave_o
.
dat
(
15
downto
0
)
<=
std_logic_vector
(
nbr_read
);
when
"011"
=>
slave_o
.
dat
(
31
downto
16
)
<=
(
others
=>
'0'
);
slave_o
.
dat
(
15
downto
0
)
<=
std_logic_vector
(
nbr_write
);
when
"100"
=>
slave_o
.
dat
<=
std_logic_vector
(
nbr_write_errors
);
when
"101"
=>
slave_o
.
err
<=
'1'
;
when
others
=>
null
;
end
case
;
when
"10"
=>
slave_o
.
dat
<=
std_logic_vector
(
counter
);
when
"11"
=>
slave_o
.
dat
<=
pattern
;
when
others
=>
null
;
end
case
;
else
slave_o
.
dat
<=
pattern
;
end
if
;
slave_o
.
ack
<=
'1'
;
end
if
;
end
if
;
...
...
software/vmecore_test/test_vme.c
View file @
18a8bc29
...
...
@@ -76,8 +76,37 @@ static volatile unsigned char *ptr8;
static
volatile
unsigned
short
*
ptr16
;
static
volatile
unsigned
int
*
ptr32
;
/* Setup the board for access mode AM. Return the base + mask. */
static
void
map_for_function
(
struct
vme_function
*
fn
)
setup_am
(
uint8_t
am
,
uint32_t
*
base
,
uint32_t
*
mask
)
{
for
(
int
i
=
0
;
i
<
8
;
i
++
)
{
struct
vme_function
fn
;
read_vme_function
(
&
fn
,
i
);
if
(
!
((
fn
.
amcap
>>
am
)
&
1
))
continue
;
*
mask
=
fn
.
adem
&
0xffffff00
;
*
base
=
(
slot
<<
19
)
&
*
mask
;
fn
.
ader
=
*
base
|
(
am
<<
2
);
fn
.
am
=
am
;
conf
[
0x7ff63
+
i
*
0x10
+
0
]
=
(
fn
.
ader
>>
24
)
&
0xff
;
conf
[
0x7ff63
+
i
*
0x10
+
4
]
=
(
fn
.
ader
>>
16
)
&
0xff
;
conf
[
0x7ff63
+
i
*
0x10
+
8
]
=
(
fn
.
ader
>>
8
)
&
0xff
;
conf
[
0x7ff63
+
i
*
0x10
+
12
]
=
(
fn
.
ader
>>
0
)
&
0xff
;
return
;
}
fprintf
(
stderr
,
"am 0x%02x is not supported
\n
"
,
am
);
exit
(
3
);
}
/* Map vme space defined by AM, BASE, MASK to user memory. */
static
void
setup_map
(
uint8_t
am
,
uint32_t
base
,
uint32_t
mask
)
{
if
(
ptr
!=
NULL
)
{
...
...
@@ -85,12 +114,10 @@ map_for_function (struct vme_function *fn)
ptr
=
NULL
;
}
uint32_t
mask
=
fn
->
adem
&
0xffffff00
;
data_map
.
am
=
fn
->
am
;
data_map
.
am
=
am
;
data_map
.
data_width
=
32
;
data_map
.
sizel
=
MAX
(
0x
1
0000
,
~
mask
+
1
);
data_map
.
vme_addrl
=
fn
->
ader
&
mask
;
data_map
.
sizel
=
MAX
(
0x
8
0000
,
~
mask
+
1
);
data_map
.
vme_addrl
=
base
&
mask
;
printf
(
"INFO: Map VME 0x%08x AM 0x%02x
\n
"
,
data_map
.
vme_addrl
,
data_map
.
am
);
...
...
@@ -107,32 +134,20 @@ map_for_function (struct vme_function *fn)
ptr32
=
(
volatile
unsigned
int
*
)
ptr
;
}
/* Setup the board for AM and map the address space. */
static
uint32_t
map_for_am
(
uint8_t
am
)
{
for
(
int
i
=
0
;
i
<
8
;
i
++
)
{
uint32_t
base
;
struct
vme_function
fn
;
read_vme_function
(
&
fn
,
i
);
if
(
!
((
fn
.
amcap
>>
am
)
&
1
))
continue
;
uint32_t
base
;
uint32_t
mask
;
base
=
(
slot
<<
19
)
&
fn
.
adem
&
0xffffff00
;
fn
.
ader
=
base
|
(
am
<<
2
);
fn
.
am
=
am
;
conf
[
0x7ff63
+
i
*
0x10
+
0
]
=
(
fn
.
ader
>>
24
)
&
0xff
;
conf
[
0x7ff63
+
i
*
0x10
+
4
]
=
(
fn
.
ader
>>
16
)
&
0xff
;
conf
[
0x7ff63
+
i
*
0x10
+
8
]
=
(
fn
.
ader
>>
8
)
&
0xff
;
conf
[
0x7ff63
+
i
*
0x10
+
12
]
=
(
fn
.
ader
>>
0
)
&
0xff
;
map_for_function
(
&
fn
);
return
base
;
}
fprintf
(
stderr
,
"am 0x%02x is not supported
\n
"
,
am
);
exit
(
3
);
setup_am
(
am
,
&
base
,
&
mask
);
setup_map
(
am
,
base
,
mask
);
return
base
;
}
static
unsigned
int
read_nbr_interrupts
(
unsigned
int
level
)
{
...
...
@@ -254,7 +269,7 @@ rate_done (struct timespec *start_ts, unsigned int nbr_bytes)
{
struct
timespec
end_ts
;
unsigned
long
nano
;
if
(
clock_gettime
(
CLOCK_MONOTONIC
,
&
end_ts
)
!=
0
)
{
fprintf
(
stderr
,
"clock_gettime error: %m
\n
"
);
...
...
@@ -283,14 +298,16 @@ do_test_error_counter (void)
}
static
void
do_test_dma
(
uint8_t
am
)
do_test_dma
_with_len
(
uint8_t
am
,
uint32_t
off
,
uint32_t
len
)
{
struct
vme_dma
dma
;
uint32_t
base
;
uint32_t
mask
;
void
*
buf
;
int
s
;
struct
timespec
start_ts
;
enum
vme_dma_block_size
bsize
;
int
nbr_err
;
// Clear pattern write error counter
map_for_am
(
0x39
);
...
...
@@ -301,17 +318,18 @@ do_test_dma (uint8_t am)
exit
(
3
);
}
printf
(
"INFO: test DMA for am 0x%02x
\n
"
,
am
);
base
=
map_for_am
(
am
);
printf
(
"INFO: test DMA for am 0x%02x (off: 0x%08x, len: 0x%0x)
\n
"
,
am
,
off
,
len
);
setup_am
(
am
,
&
base
,
&
mask
);
if
(
posix_memalign
(
&
buf
,
4096
,
0x4000
)
!=
0
)
if
(
posix_memalign
(
&
buf
,
4096
,
len
)
!=
0
)
{
fprintf
(
stderr
,
"cannot allocate memory
\n
"
);
exit
(
4
);
}
memset
(
&
dma
,
0
,
sizeof
(
dma
));
dma
.
status
=
0
;
// ??
dma
.
length
=
0x4000
;
dma
.
length
=
len
;
dma
.
novmeinc
=
0
;
dma
.
dir
=
VME_DMA_FROM_DEVICE
;
...
...
@@ -320,7 +338,7 @@ do_test_dma (uint8_t am)
dma
.
src
.
v2esst_mode
=
VME_SST160
;
dma
.
src
.
bcast_select
=
0
;
dma
.
src
.
addru
=
0
;
dma
.
src
.
addrl
=
base
+
0xc000
;
dma
.
src
.
addrl
=
base
+
off
;
dma
.
dst
.
addru
=
((
uintptr_t
)
buf
)
>>
32
;
dma
.
dst
.
addrl
=
(
uintptr_t
)
buf
;
...
...
@@ -340,32 +358,35 @@ do_test_dma (uint8_t am)
rate_start
(
&
start_ts
);
s
=
vme_dma_read
(
&
dma
);
rate_done
(
&
start_ts
,
0x4000
);
rate_done
(
&
start_ts
,
len
);
report
(
s
==
0
,
"DMA read"
);
for
(
int
j
=
0
;
j
<
0x1000
;
j
++
)
nbr_err
=
0
;
for
(
int
j
=
0
;
j
<
len
/
4
;
j
++
)
{
uint32_t
r
=
((
uint32_t
*
)
buf
)[
j
];
uint32_t
v
;
v
=
(
j
+
0x3000
)
;
v
=
(
j
+
(
off
>>
2
))
&
0xffff
;
v
=
v
|
(
~
v
<<
16
);
r
=
swapbe32
(
r
);
if
(
r
!=
v
)
{
report
(
false
,
"dma read error at 0x%04x (got 0x%08x, expect 0x%08x)"
,
j
<<
2
,
r
,
v
);
break
;
"dma read error at 0x%08x (got 0x%08x, expect 0x%08x)"
,
off
+
(
j
<<
2
),
r
,
v
);
nbr_err
++
;
if
(
nbr_err
>=
10
)
break
;
}
}
// DMA write
memset
(
&
dma
,
0
,
sizeof
(
dma
));
dma
.
status
=
0
;
// ??
dma
.
length
=
0x4000
;
dma
.
length
=
len
;
dma
.
novmeinc
=
0
;
dma
.
dir
=
VME_DMA_TO_DEVICE
;
...
...
@@ -377,7 +398,7 @@ do_test_dma (uint8_t am)
dma
.
dst
.
v2esst_mode
=
VME_SST160
;
dma
.
dst
.
bcast_select
=
0
;
dma
.
dst
.
addru
=
0
;
dma
.
dst
.
addrl
=
base
+
0xc000
;
dma
.
dst
.
addrl
=
base
+
off
;
dma
.
ctrl
.
vme_block_size
=
bsize
;
dma
.
ctrl
.
vme_backoff_time
=
VME_DMA_BACKOFF_1
;
...
...
@@ -386,7 +407,7 @@ do_test_dma (uint8_t am)
rate_start
(
&
start_ts
);
s
=
vme_dma_write
(
&
dma
);
rate_done
(
&
start_ts
,
0x4000
);
rate_done
(
&
start_ts
,
len
);
report
(
s
==
0
,
"DMA write"
);
free
(
buf
);
...
...
@@ -396,6 +417,12 @@ do_test_dma (uint8_t am)
swapbe32
(
ptr32
[
0x1004
]));
}
static
void
do_test_dma
(
uint8_t
am
)
{
do_test_dma_with_len
(
am
,
0xc000
,
0x4000
);
}
static
void
do_irq_test
(
void
)
{
...
...
@@ -526,7 +553,7 @@ do_leds (void)
int
main
(
int
argc
,
char
**
argv
)
{
int
am
=
VME_A24_USER_DATA_SCT
;
int
am
=
-
1
;
int
c
;
struct
vme_mapping
conf_map
;
char
*
action
=
NULL
;
...
...
@@ -544,7 +571,7 @@ main (int argc, char **argv)
action
=
optarg
;
break
;
case
'h'
:
printf
(
"usage: %s [-h] [-a ACTION] -s SLOT
\n
"
,
argv
[
0
]);
printf
(
"usage: %s [-h] [-
m AM] [-
a ACTION] -s SLOT
\n
"
,
argv
[
0
]);
return
0
;
case
'?'
:
fprintf
(
stderr
,
"incorrect option, try %s -h
\n
"
,
argv
[
0
]);
...
...
@@ -557,6 +584,7 @@ main (int argc, char **argv)
return
2
;
}
/* Map the CR/CSR space of the board. */
memset
(
&
conf_map
,
0
,
sizeof
(
conf_map
));
conf_map
.
am
=
VME_CR_CSR
;
conf_map
.
data_width
=
32
;
...
...
@@ -571,33 +599,44 @@ main (int argc, char **argv)
}
if
(
action
==
NULL
)
return
do_vme_test
();
/* Find a map. */
memset
(
&
data_map
,
0
,
sizeof
(
data_map
));
{
printf
(
"No action, testing vme...
\n
"
);
return
do_vme_test
();
}
else
printf
(
"action: %s
\n
"
,
action
);
for
(
int
i
=
0
;
i
<
8
;
i
++
)
/* Set an am if needed. */
if
(
am
!=
-
1
)
map_for_am
(
am
);
else
{
struct
vme_function
fn
;
/* Find a map. */
memset
(
&
data_map
,
0
,
sizeof
(
data_map
));
read_vme_function
(
&
fn
,
i
);
for
(
int
i
=
0
;
i
<
8
;
i
++
)
{
struct
vme_function
fn
;
if
(
!
is_vme_function_enabled
(
&
fn
))
continue
;
read_vme_function
(
&
fn
,
i
);
uint32_t
mask
=
fn
.
adem
&
0xffffff00
;
if
(
!
is_vme_function_enabled
(
&
fn
))
continue
;
printf
(
"Func %d: 0x%08x-0x%08x, am=0x%02x
\n
"
,
i
,
fn
.
ader
&
mask
,
(
fn
.
ader
&
mask
)
|
~
mask
,
fn
.
am
)
;
uint32_t
mask
=
fn
.
adem
&
0xffffff00
;
uint32_t
base
=
fn
.
ader
&
mask
;
map_for_function
(
&
fn
);
break
;
}
printf
(
"Func %d: 0x%08x-0x%08x, am=0x%02x
\n
"
,
i
,
base
,
(
base
&
mask
)
|
~
mask
,
fn
.
am
);
setup_map
(
fn
.
am
,
base
,
mask
);
break
;
}
if
(
data_map
.
data_width
==
0
)
{
printf
(
"No map found
\n
"
);
return
3
;
if
(
data_map
.
data_width
==
0
)
{
printf
(
"No map found
\n
"
);
return
3
;
}
}
if
(
action
==
NULL
||
strcmp
(
action
,
"simple"
)
==
0
)
...
...
@@ -736,7 +775,7 @@ main (int argc, char **argv)
unsigned
int
i
,
j
;
unsigned
int
cnt
=
4
*
16
;
struct
timespec
start_ts
;
rate_start
(
&
start_ts
);
for
(
j
=
0
;
j
<
cnt
;
j
++
)
for
(
i
=
0xc000
;
i
<=
0xffff
;
i
+=
4
)
...
...
@@ -750,6 +789,18 @@ main (int argc, char **argv)
printf
(
"irq vector: %d
\n
"
,
conf
[
0x7ff5f
]);
printf
(
"nbr ints: %u
\n
"
,
read_nbr_interrupts
(
level
));
}
else
if
(
strcmp
(
action
,
"blt"
)
==
0
)
{
if
(
am
==
-
1
)
am
=
0x39
;
do_test_dma_with_len
(
am
,
0x10000
,
0x40000
);
}
else
if
(
strcmp
(
action
,
"mblt"
)
==
0
)
{
if
(
am
==
-
1
)
am
=
0x38
;
do_test_dma_with_len
(
am
,
0x10000
,
0x40000
);
}
else
printf
(
"unknown action
\n
"
);
...
...
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