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
7be72cf4
Commit
7be72cf4
authored
Jan 27, 2020
by
Tristan Gingold
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
software: update test_vme, add vmedma and vmespy.
parent
d2d407bd
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
600 additions
and
9 deletions
+600
-9
Makefile
software/vmecore_test/Makefile
+15
-5
test_vme.c
software/vmecore_test/test_vme.c
+4
-4
vmedma.c
software/vmecore_test/vmedma.c
+225
-0
vmespy.c
software/vmecore_test/vmespy.c
+356
-0
No files found.
software/vmecore_test/Makefile
View file @
7be72cf4
VMEBRIDGE
=
/acc/local/L867/drv/vmebus/1.0.1
CC
=
gcc
CFLAGS
=
-O
-Wall
CFLAGS
=
-O
-Wall
--std
=
c99
-I
$(VMEBRIDGE)
/include/vmebus
LDFLAGS
=
$(VMEBRIDGE)
/lib/libvmebus.a
-lrt
all
:
test_vme
all
:
test_vme
vmespy vmedma
test_vme
:
test_vme.o
$(CC)
-o
$@
$<
$(
VMEBRIDGE)
/lib/libvmebus.a
-lrt
$(CC)
-o
$@
$<
$(
LDFLAGS)
test_vme.o
:
test_vme.c
$(CC)
-c
-o
$@
$<
--std
=
c99
$(CFLAGS)
-I
$(VMEBRIDGE)
/include/vmebus
vmespy
:
vmespy.o
$(CC)
-o
$@
$<
$(LDFLAGS)
vmespy.o
:
vmespy.c
vmedma.o
:
vmedma.c
vmedma
:
vmedma.o
$(CC)
-o
$@
$<
$(LDFLAGS)
clean
:
$(RM)
-f
test_vme
*
.o
*
~
*
.pyc
$(RM)
-f
test_vme
vmedma vmespy
*
.o
*
~
*
.pyc
software/vmecore_test/test_vme.c
View file @
7be72cf4
...
...
@@ -368,7 +368,7 @@ do_test_dma_with_len (uint8_t am, uint32_t off, uint32_t len)
uint32_t
r
=
((
uint32_t
*
)
buf
)[
j
];
uint32_t
v
;
v
=
(
j
+
(
off
>>
2
)
)
&
0xffff
;
v
=
(
(
j
<<
2
)
+
off
)
&
0xffff
;
v
=
v
|
(
~
v
<<
16
);
r
=
swapbe32
(
r
);
...
...
@@ -474,7 +474,7 @@ static int
do_vme_test
(
void
)
{
// A24
do_test_mem
(
0x3e
);
//
do_test_mem (0x3e);
do_test_mem
(
0x39
);
// A32
...
...
@@ -813,13 +813,13 @@ main (int argc, char **argv)
{
if
(
am
==
-
1
)
am
=
0x39
;
do_test_dma_with_len
(
am
,
0x
1
0000
,
0x40000
);
do_test_dma_with_len
(
am
,
0x
2
0000
,
0x40000
);
}
else
if
(
strcmp
(
action
,
"mblt"
)
==
0
)
{
if
(
am
==
-
1
)
am
=
0x38
;
do_test_dma_with_len
(
am
,
0x
1
0000
,
0x40000
);
do_test_dma_with_len
(
am
,
0x
2
0000
,
0x40000
);
}
else
if
(
strcmp
(
action
,
"show-func"
)
==
0
)
do_show_functions
();
...
...
software/vmecore_test/vmedma.c
0 → 100644
View file @
7be72cf4
/*
* Copyright (C) 2017 CERN (www.cern.ch)
* Author: Tristan Gingold <tristan.gingold@cern.ch>
*
* Released according to the GNU GPL, version 2 or any later version.
*
* test_vme: program to test the vme64xcore (to be used with the
* svec_vmecore_test design).
*/
#define _XOPEN_SOURCE 600
#define _GNU_SOURCE 1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <unistd.h>
#include <getopt.h>
#include "libvmebus.h"
#define VME_2eVME 3
static
enum
vme_dma_block_size
vbs
=
VME_DMA_BSIZE_2048
;
static
enum
vme_dma_block_size
pbs
=
VME_DMA_BSIZE_2048
;
static
void
rate_start
(
struct
timespec
*
start_ts
)
{
if
(
clock_gettime
(
CLOCK_MONOTONIC
,
start_ts
)
!=
0
)
{
fprintf
(
stderr
,
"clock_gettime error: %m
\n
"
);
exit
(
4
);
}
}
static
void
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
"
);
exit
(
4
);
}
nano
=
(
end_ts
.
tv_nsec
-
start_ts
->
tv_nsec
)
+
(
end_ts
.
tv_sec
-
start_ts
->
tv_sec
)
*
1000000000
;
printf
(
"Rate: %f MB/sec
\n
"
,
nbr_bytes
*
1.0E9
/
nano
/
(
1
<<
20
));
}
static
enum
vme_dma_block_size
convert_block_size
(
const
char
*
str
)
{
unsigned
int
num
;
num
=
strtoul
(
str
,
NULL
,
0
);
switch
(
num
)
{
case
32
:
return
VME_DMA_BSIZE_32
;
case
64
:
return
VME_DMA_BSIZE_64
;
case
128
:
return
VME_DMA_BSIZE_128
;
case
256
:
return
VME_DMA_BSIZE_256
;
case
512
:
return
VME_DMA_BSIZE_512
;
case
1024
:
return
VME_DMA_BSIZE_1024
;
case
2048
:
return
VME_DMA_BSIZE_2048
;
case
4096
:
return
VME_DMA_BSIZE_4096
;
default:
fprintf
(
stderr
,
"unhandled block size of %u
\n
"
,
num
);
exit
(
2
);
}
}
static
void
do_test_dma
(
uint8_t
am
,
enum
vme_2esst_mode
vmode
,
uint32_t
addr
,
uint32_t
len
,
void
*
buf
)
{
struct
vme_dma
dma
;
int
s
;
struct
timespec
start_ts
;
memset
(
&
dma
,
0
,
sizeof
(
dma
));
dma
.
status
=
0
;
// ??
dma
.
length
=
len
;
dma
.
novmeinc
=
0
;
dma
.
dir
=
VME_DMA_FROM_DEVICE
;
dma
.
src
.
data_width
=
VME_D32
;
dma
.
src
.
am
=
am
;
dma
.
src
.
v2esst_mode
=
vmode
;
dma
.
src
.
bcast_select
=
0
;
dma
.
src
.
addru
=
0
;
dma
.
src
.
addrl
=
addr
;
dma
.
dst
.
addru
=
((
uintptr_t
)
buf
)
>>
32
;
dma
.
dst
.
addrl
=
(
uintptr_t
)
buf
;
dma
.
ctrl
.
vme_block_size
=
vbs
;
dma
.
ctrl
.
vme_backoff_time
=
VME_DMA_BACKOFF_0
;
dma
.
ctrl
.
pci_block_size
=
pbs
;
dma
.
ctrl
.
pci_backoff_time
=
VME_DMA_BACKOFF_0
;
rate_start
(
&
start_ts
);
s
=
vme_dma_read
(
&
dma
);
rate_done
(
&
start_ts
,
len
);
if
(
s
!=
0
)
printf
(
"Error: DMA read
\n
"
);
}
#define OPT_VBS 128
#define OPT_PBS 129
static
const
struct
option
options
[]
=
{
{
"addr"
,
required_argument
,
0
,
'a'
},
{
"len"
,
required_argument
,
0
,
'n'
},
{
"am"
,
required_argument
,
0
,
'm'
},
{
"2e"
,
required_argument
,
0
,
'e'
},
{
"dump"
,
no_argument
,
0
,
'd'
},
{
"help"
,
no_argument
,
0
,
'h'
},
{
"vbs"
,
required_argument
,
0
,
OPT_VBS
},
{
"pbs"
,
required_argument
,
0
,
OPT_PBS
},
{
NULL
,
0
,
0
,
0
}
};
int
main
(
int
argc
,
char
**
argv
)
{
int
am
=
0x08
;
/* MBLT */
unsigned
addr
=
0xffffffff
;
unsigned
int
len
=
4096
;
int
c
;
void
*
buf
;
int
flag_dump
=
0
;
enum
vme_2esst_mode
vmode
=
VME_2eVME
;
while
((
c
=
getopt_long
(
argc
,
argv
,
"a:m:n:e:dh"
,
options
,
NULL
))
!=
-
1
)
switch
(
c
)
{
case
'a'
:
addr
=
strtoul
(
optarg
,
NULL
,
0
);
break
;
case
'n'
:
len
=
strtol
(
optarg
,
NULL
,
0
);
break
;
case
'm'
:
am
=
strtol
(
optarg
,
NULL
,
0
);
break
;
case
'e'
:
if
(
strcmp
(
optarg
,
"2eVME"
)
==
0
)
vmode
=
VME_2eVME
;
else
if
(
strcmp
(
optarg
,
"2eSST"
)
==
0
||
strcmp
(
optarg
,
"2eSST-160"
)
==
0
)
vmode
=
VME_SST160
;
else
{
fprintf
(
stderr
,
"%s: bad value for -e, expect 2eVME or 2eSST-160
\n
"
,
argv
[
0
]);
exit
(
3
);
}
break
;
case
'd'
:
flag_dump
=
1
;
break
;
case
OPT_VBS
:
vbs
=
convert_block_size
(
optarg
);
break
;
case
OPT_PBS
:
pbs
=
convert_block_size
(
optarg
);
break
;
case
'h'
:
printf
(
"usage: %s -a ADDR [-m AM] [-n LENGTH]
\n
"
,
argv
[
0
]);
printf
(
"options:
\n
"
" --vbs=SZ set VME block size
\n
"
" --pbs=SZ set PCI block size
\n
"
);
return
0
;
case
'?'
:
fprintf
(
stderr
,
"incorrect option, try %s -h
\n
"
,
argv
[
0
]);
return
1
;
}
if
(
addr
==
0xffffffff
)
{
fprintf
(
stderr
,
"missing address
\n
"
);
return
2
;
}
if
(
am
==
0
)
{
fprintf
(
stderr
,
"bad AM
\n
"
);
return
2
;
}
printf
(
"INFO: test DMA for am 0x%02x, mode: %u (addr: 0x%08x, len: 0x%0x)
\n
"
,
am
,
vmode
,
addr
,
len
);
if
(
posix_memalign
(
&
buf
,
4096
,
len
)
!=
0
)
{
fprintf
(
stderr
,
"cannot allocate memory
\n
"
);
exit
(
4
);
}
do_test_dma
(
am
,
vmode
,
addr
,
len
,
buf
);
if
(
flag_dump
)
for
(
unsigned
i
=
0
;
i
<
len
;
i
+=
4
)
printf
(
"%08x: %08x
\n
"
,
addr
+
i
,
((
unsigned
int
*
)
buf
)[
i
>>
2
]);
free
(
buf
);
return
0
;
}
software/vmecore_test/vmespy.c
0 → 100644
View file @
7be72cf4
/*
* Copyright (C) 2017 CERN (www.cern.ch)
* Author: Tristan Gingold <tristan.gingold@cern.ch>
*
* Released according to the GNU GPL, version 2 or any later version.
*
* test_vme: program to test the vme64xcore (to be used with the
* svec_vmecore_test design).
*/
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include "libvmebus.h"
#define MAX(A, B) ((A) > (B) ? (A) : (B))
static
unsigned
slot
=
0
;
static
volatile
unsigned
char
*
conf
;
static
uint32_t
read_conf4
(
unsigned
int
addr
)
{
return
(
conf
[
addr
+
0
]
<<
24
)
|
(
conf
[
addr
+
4
]
<<
16
)
|
(
conf
[
addr
+
8
]
<<
8
)
|
(
conf
[
addr
+
12
]
<<
0
);
}
static
uint64_t
read_conf8
(
unsigned
int
addr
)
{
uint64_t
res
=
0
;
for
(
int
i
=
0
;
i
<
8
;
i
++
)
res
=
(
res
<<
8
)
|
conf
[
addr
+
i
*
4
];
return
res
;
}
struct
vme_function
{
uint64_t
amcap
;
uint32_t
adem
;
uint32_t
ader
;
uint8_t
am
;
};
static
void
read_vme_function
(
struct
vme_function
*
res
,
int
i
)
{
res
->
amcap
=
read_conf8
(
0x123
+
i
*
0x20
);
res
->
adem
=
read_conf4
(
0x623
+
i
*
0x10
);
res
->
ader
=
read_conf4
(
0x7ff63
+
i
*
0x10
);
res
->
am
=
(
res
->
ader
>>
2
)
&
0x3f
;
}
static
bool
is_vme_function_enabled
(
struct
vme_function
*
f
)
{
if
((
f
->
ader
&
1
)
||
((
f
->
amcap
>>
f
->
am
)
&
1
)
==
0
)
return
false
;
else
return
true
;
}
static
struct
vme_mapping
data_map
;
static
void
*
ptr
;
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
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
)
{
vme_unmap
(
&
data_map
,
1
);
ptr
=
NULL
;
}
data_map
.
am
=
am
;
data_map
.
data_width
=
32
;
data_map
.
sizel
=
MAX
(
0x80000
,
~
mask
+
1
);
data_map
.
vme_addrl
=
base
&
mask
;
printf
(
"INFO: Map VME 0x%08x AM 0x%02x (base=%08x mask=%08x)
\n
"
,
data_map
.
vme_addrl
,
data_map
.
am
,
base
,
mask
);
/* Do mmap */
ptr
=
vme_map
(
&
data_map
,
1
);
if
(
!
ptr
)
{
fprintf
(
stderr
,
"cannot map vme space
\n
"
);
exit
(
3
);
}
ptr8
=
(
volatile
unsigned
char
*
)
ptr
;
ptr16
=
(
volatile
unsigned
short
*
)
ptr
;
ptr32
=
(
volatile
unsigned
int
*
)
ptr
;
}
/* Setup the board for AM and map the address space. */
static
uint32_t
map_for_am
(
uint8_t
am
)
{
uint32_t
base
;
uint32_t
mask
;
setup_am
(
am
,
&
base
,
&
mask
);
setup_map
(
am
,
base
,
mask
);
return
base
;
}
static
void
map_crcsr
(
void
)
{
struct
vme_mapping
conf_map
;
/* 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
;
conf_map
.
sizel
=
512
<<
10
;
conf_map
.
vme_addrl
=
(
slot
<<
19
);
conf
=
vme_map
(
&
conf_map
,
1
);
if
(
!
conf
)
{
fprintf
(
stderr
,
"cannot map vme CR/CSR space
\n
"
);
exit
(
3
);
}
}
void
do_show_functions
(
void
)
{
int
i
;
for
(
i
=
0
;
i
<
8
;
i
++
)
{
int
j
;
struct
vme_function
fn
;
read_vme_function
(
&
fn
,
i
);
printf
(
"F%d: amcap=%016lx adem=%08x ader=%08x am=%02x
\n
"
,
i
,
fn
.
amcap
,
fn
.
adem
,
fn
.
ader
,
fn
.
am
);
for
(
j
=
0
;
j
<
64
;
j
++
)
if
((
fn
.
amcap
>>
j
)
&
1
)
printf
(
" %02x"
,
j
);
printf
(
"
\n
"
);
}
}
int
main
(
int
argc
,
char
**
argv
)
{
int
am
=
-
1
;
int
c
;
char
*
action
=
NULL
;
while
((
c
=
getopt
(
argc
,
argv
,
"s:ha:m:"
))
!=
-
1
)
switch
(
c
)
{
case
's'
:
slot
=
strtol
(
optarg
,
NULL
,
0
);
break
;
case
'm'
:
am
=
strtol
(
optarg
,
NULL
,
0
);
break
;
case
'a'
:
action
=
optarg
;
break
;
case
'h'
:
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
]);
return
1
;
}
if
(
slot
==
0
||
slot
>
0x1f
)
{
fprintf
(
stderr
,
"incorrect or missing slot value (1 - 0x1f)
\n
"
);
return
2
;
}
if
(
strcmp
(
action
,
"set-ader"
)
==
0
)
{
unsigned
int
fn
;
uint32_t
addr
;
unsigned
int
am
;
if
(
optind
+
3
!=
argc
)
{
fprintf
(
stderr
,
"usage: set-adr FN ADDR AM
\n
"
);
exit
(
2
);
}
fn
=
strtoul
(
argv
[
optind
+
0
],
NULL
,
0
);
addr
=
strtoul
(
argv
[
optind
+
1
],
NULL
,
0
);
am
=
strtoul
(
argv
[
optind
+
2
],
NULL
,
0
);
printf
(
"setting ader %u to %08x %02x
\n
"
,
fn
,
addr
,
am
);
map_crcsr
();
addr
|=
am
<<
2
;
conf
[
0x7ff63
+
fn
*
0x10
+
0
]
=
(
addr
>>
24
)
&
0xff
;
conf
[
0x7ff63
+
fn
*
0x10
+
4
]
=
(
addr
>>
16
)
&
0xff
;
conf
[
0x7ff63
+
fn
*
0x10
+
8
]
=
(
addr
>>
8
)
&
0xff
;
conf
[
0x7ff63
+
fn
*
0x10
+
12
]
=
(
addr
>>
0
)
&
0xff
;
return
0
;
}
else
if
(
strcmp
(
action
,
"show-ader"
)
==
0
)
{
map_crcsr
();
do_show_functions
();
return
0
;
}
if
(
am
==
-
1
)
{
/* MAP A24. */
setup_map
(
0x39
,
slot
<<
19
,
~
((
512
<<
10
)
-
1
));
}
else
{
map_crcsr
();
/* Set an am if needed. */
if
(
am
!=
-
1
)
map_for_am
(
am
);
else
{
/* Find a map. */
memset
(
&
data_map
,
0
,
sizeof
(
data_map
));
for
(
int
i
=
0
;
i
<
8
;
i
++
)
{
struct
vme_function
fn
;
read_vme_function
(
&
fn
,
i
);
if
(
!
is_vme_function_enabled
(
&
fn
))
continue
;
uint32_t
mask
=
fn
.
adem
&
0xffffff00
;
uint32_t
base
=
fn
.
ader
&
mask
;
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
(
action
==
NULL
||
strcmp
(
action
,
"regs"
)
==
0
)
{
printf
(
"spy status: 0x%08x
\n
"
,
swapbe32
(
ptr32
[
0x4018
>>
2
]));
printf
(
"spy counters: 0x%08x
\n
"
,
swapbe32
(
ptr32
[
0x401c
>>
2
]));
}
else
if
(
strcmp
(
action
,
"count"
)
==
0
)
{
ptr32
[
0x401c
>>
2
]
=
swapbe32
(
20
);
}
else
if
(
strcmp
(
action
,
"start"
)
==
0
)
{
unsigned
int
count
=
10
;
if
(
optind
<
argc
)
count
=
atoi
(
argv
[
optind
]);
printf
(
"Start trigger after %u transfers
\n
"
,
count
);
ptr32
[
0x401c
>>
2
]
=
swapbe32
(
count
);
ptr32
[
0x4018
>>
2
]
=
swapbe32
(
1
);
}
else
if
(
strcmp
(
action
,
"dump"
)
==
0
)
{
unsigned
last
;
unsigned
addr
,
prev_addr
;
unsigned
data
,
prev_data
;
unsigned
ctrl
,
prev_ctrl
;
last
=
swapbe32
(
ptr32
[
0x401c
>>
2
])
>>
16
;
prev_ctrl
=
0
;
prev_data
=
0
;
prev_addr
=
0
;
printf
(
"cycl: data addr am as wr ds dtack
\n
"
);
for
(
unsigned
i
=
0
;
i
<=
last
;
i
++
)
{
data
=
swapbe32
(
ptr32
[(
0x20000
+
(
i
<<
4
)
+
0
)
>>
2
]);
addr
=
swapbe32
(
ptr32
[(
0x20000
+
(
i
<<
4
)
+
4
)
>>
2
]);
ctrl
=
swapbe32
(
ptr32
[(
0x20000
+
(
i
<<
4
)
+
8
)
>>
2
]);
if
(
ctrl
!=
prev_ctrl
||
addr
!=
prev_addr
||
data
!=
prev_data
)
{
printf
(
"%04u: "
,
i
);
printf
(
" %08x"
,
data
);
printf
(
" %08x"
,
addr
^
1
);
printf
(
" %02x %x %x %x %x
\n
"
,
ctrl
&
0x3f
,
(
ctrl
>>
10
)
&
1
,
(
ctrl
>>
8
)
&
1
,
(
ctrl
>>
6
)
&
3
,
(
ctrl
>>
11
)
&
1
);
prev_ctrl
=
ctrl
;
prev_addr
=
addr
;
prev_data
=
data
;
}
}
}
else
printf
(
"unknown action
\n
"
);
return
0
;
}
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