Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
L
linux-elphel
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Commits
Open sidebar
Elphel
linux-elphel
Commits
3c64c8e0
Commit
3c64c8e0
authored
Apr 26, 2016
by
Mikhail Karpenko
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
WIP: fix 32 bytes offset
parent
a89358c3
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
201 additions
and
17 deletions
+201
-17
circbuf.c
src/drivers/elphel/circbuf.c
+146
-13
circbuf.h
src/drivers/elphel/circbuf.h
+4
-0
sensor_common.c
src/drivers/elphel/sensor_common.c
+50
-4
c313a.h
src/include/elphel/c313a.h
+1
-0
No files found.
src/drivers/elphel/circbuf.c
View file @
3c64c8e0
...
...
@@ -279,12 +279,27 @@ void dump_interframe_params(struct interframe_params_t *params, int offset)
*/
unsigned
long
get_image_length
(
int
byte_offset
,
unsigned
int
chn
,
int
*
last_chunk_offset
)
{
unsigned
int
offset
;
unsigned
long
len32
;
int
last_image_chunk
=
byte_offset
-
OFFSET_X40
;
if
(
last_image_chunk
<
0
)
last_image_chunk
+=
CCAM_DMA_SIZE
;
len32
=
circbuf_priv
[
chn
].
buf_ptr
[
BYTE2DW
(
last_image_chunk
+
(
CHUNK_SIZE
-
CCAM_MMAP_META_LENGTH
))];
int
last_image_chunk
=
X393_BUFFSUB
(
byte_offset
,
OFFSET_X40
);
// if (last_image_chunk < 0)
// last_image_chunk += CCAM_DMA_SIZE;
// len32 = circbuf_priv[chn].buf_ptr[BYTE2DW(last_image_chunk + (CHUNK_SIZE - CCAM_MMAP_META_LENGTH))];
offset
=
last_image_chunk
+
(
CHUNK_SIZE
-
CCAM_MMAP_META_LENGTH
);
len32
=
circbuf_priv
[
chn
].
buf_ptr
[
BYTE2DW
(
offset
)];
if
((
len32
&
MARKER_FF
)
!=
MARKER_FF
)
{
printk
(
KERN_DEBUG
"failed to get 0xff marker at offset 0x%x
\n
"
,
offset
);
byte_offset
=
X393_BUFFSUB
(
byte_offset
,
0x20
);
last_image_chunk
=
X393_BUFFSUB
(
byte_offset
,
OFFSET_X40
);
offset
=
last_image_chunk
+
(
CHUNK_SIZE
-
CCAM_MMAP_META_LENGTH
);
len32
=
circbuf_priv
[
chn
].
buf_ptr
[
BYTE2DW
(
offset
)];
if
((
len32
&
MARKER_FF
)
!=
MARKER_FF
)
{
printk
(
KERN_DEBUG
"failed to get 0xff marker at CORRECTED offset 0x%x
\n
"
,
offset
);
return
0
;
}
}
dev_dbg
(
g_dev_ptr
,
"got len32 = 0x%lx at 0x%x
\n
"
,
len32
,
last_image_chunk
+
(
CHUNK_SIZE
-
CCAM_MMAP_META_LENGTH
));
...
...
@@ -301,6 +316,7 @@ unsigned long get_image_length(int byte_offset, unsigned int chn, int *last_chun
* frame header before \e rp and will point to its parameters
* @param[in] chn specify compressor channel number which pointer should be checked
* @return 0 if the pointer is for the frame yet to be acquired, 1 if there is a valid frame at this address,
* 2 if file pointer should be advanced by 32 bytes,
* -1 if there is no frame at this index, -2 if the pointer is not 32-bytes aligned
* sets *fpp to the frame header, including signature and length
*/
...
...
@@ -311,7 +327,7 @@ int circbuf_valid_ptr(int rp, struct interframe_params_t **fpp, unsigned int chn
unsigned
int
usec
;
int
wp
=
camseq_get_jpeg_wp
(
chn
);
unsigned
int
len32
=
get_image_length
(
DW2BYTE
(
wp
),
chn
,
&
last_image_chunk
);
struct
interframe_params_t
*
fp
;
struct
interframe_params_t
*
fp
,
*
fp_off
;
if
(
rp
&
0x1f
)
{
// rp is not 32-bytes aligned
...
...
@@ -328,13 +344,33 @@ int circbuf_valid_ptr(int rp, struct interframe_params_t **fpp, unsigned int chn
return
0
;
if
(
fp
->
signffff
!=
MARKER_FFFF
)
{
dev_dbg
(
g_dev_ptr
,
"interframe signature is overwritten
\n
"
);
return
-
1
;
dev_dbg
(
g_dev_ptr
,
"interframe signature is overwritten, signffff = 0x%x
\n
"
,
fp
->
signffff
);
fp_off
=
(
struct
interframe_params_t
*
)
&
circbuf_priv
[
chn
].
buf_ptr
[
BYTE2DW
(
rp
)];
if
(
fp_off
->
signffff
!=
MARKER_FFFF
)
{
dev_dbg
(
g_dev_ptr
,
"interframe signature is overwritten at CORRECTED offset, signffff = 0x%x
\n
"
,
fp_off
->
signffff
);
return
-
1
;
}
else
{
dev_dbg
(
g_dev_ptr
,
"interframe pointer is advanced by 0x20
\n
"
);
*
fpp
=
fp_off
;
dump_interframe_params
(
fp_off
,
rp
);
return
2
;
}
}
return
1
;
}
/**
* @brief Get image start offset pointed by its last data chunk
* @param[in] last_chunk_offset offset of the last image data chunk
* @param[in] len32 length of image
* @return image start offset
*/
inline
int
get_image_start
(
int
last_chunk_offset
,
unsigned
int
len32
)
{
return
X393_BUFFSUB
(
last_chunk_offset
+
CHUNK_SIZE
-
INSERTED_BYTES
(
len32
)
-
CCAM_MMAP_META
,
len32
);
}
/**
* @brief Reposition read/write file offset
*
...
...
@@ -380,6 +416,62 @@ int circbuf_valid_ptr(int rp, struct interframe_params_t **fpp, unsigned int chn
* @param[in] orig origin
* @return current file pointer position if operation was successful and error code otherwise
*/
/* debug code follows */
void
stop_compressor
(
unsigned
int
chn
)
{
x393_cmprs_mode_t
mode
;
mode
.
run
=
1
;
mode
.
run_set
=
1
;
x393_cmprs_control_reg
(
mode
,
chn
);
}
void
dump_state
(
unsigned
int
chn
)
{
int
img_start
,
last_image_chunk
;
int
len32
;
int
prev_ptr
,
prevprev_ptr
;
int
read_ptr
=
DW2BYTE
(
camseq_get_jpeg_wp
(
chn
));
unsigned
int
nf
,
nz
;
struct
interframe_params_t
*
fp
;
nf
=
0
;
nz
=
1
;
printk
(
KERN_DEBUG
"=== start of state dump, chn = %d ===
\n
"
,
chn
);
printk
(
KERN_DEBUG
"hardware pointer at 0x%x
\n
"
,
read_ptr
);
// move to the beginning of last frame
len32
=
get_image_length
(
read_ptr
,
chn
,
&
last_image_chunk
);
if
((
len32
&
MARKER_FF
)
!=
MARKER_FF
)
{
printk
(
KERN_DEBUG
"last acquired frame at location 0x%x is damaged
\n
"
,
read_ptr
);
return
;
}
len32
&=
FRAME_LENGTH_MASK
;
//img_start = X393_BUFFSUB(last_image_chunk + CHUNK_SIZE - INSERTED_BYTES(len32) - CCAM_MMAP_META, len32);
img_start
=
get_image_start
(
last_image_chunk
,
len32
);
read_ptr
=
img_start
;
// move back in history
while
((
circbuf_valid_ptr
(
read_ptr
,
&
fp
,
chn
)
>=
0
)
&&
(
nz
>=
0
))
{
printk
(
KERN_DEBUG
"analyzing frame starting at 0x%x
\n
"
,
read_ptr
);
//printk(KERN_DEBUG "mem dump of 0x40 bytes at (pointer - 0x20) = 0x%x:\n", read_ptr - OFFSET_X40 / 2);
//print_hex_dump_bytes("\t\t", DUMP_PREFIX_OFFSET, &circbuf_priv[chn].buf_ptr[BYTE2DW(read_ptr - OFFSET_X40 / 2)], OFFSET_X40);
nf
++
;
prevprev_ptr
=
prev_ptr
;
prev_ptr
=
read_ptr
;
len32
=
get_image_length
(
read_ptr
,
chn
,
&
last_image_chunk
);
if
((
len32
&
MARKER_FF
)
!=
MARKER_FF
)
{
printk
(
KERN_DEBUG
"
\t\t
no image before 0x%x
\n
"
,
read_ptr
);
break
;
}
len32
&=
FRAME_LENGTH_MASK
;
//printk(KERN_DEBUG "\t\tgot len32 = 0x%x", len32);
//img_start = X393_BUFFSUB(last_image_chunk + CHUNK_SIZE - INSERTED_BYTES(len32) - CCAM_MMAP_META, len32);
img_start
=
get_image_start
(
last_image_chunk
,
len32
);
read_ptr
=
img_start
;
if
(
read_ptr
>
prev_ptr
)
nz
--
;
}
printk
(
KERN_DEBUG
"=== end of state dump, %d frame(s) analyzed ===
\n
"
,
nf
);
}
/* end of debug code */
loff_t
circbuf_lseek
(
struct
file
*
file
,
loff_t
offset
,
int
orig
)
{
unsigned
int
len32
=
0
;
...
...
@@ -418,6 +510,18 @@ loff_t circbuf_lseek(struct file *file, loff_t offset, int orig)
case
LSEEK_CIRC_USED
:
if
((
fvld
=
circbuf_valid_ptr
(
file
->
f_pos
,
&
fp
,
chn
))
<
0
)
return
-
EINVAL
;
// no frames at the specified location
break
;
/* debug code follows */
case
LSEEK_CIRC_STOP_COMPRESSOR
:
{
int
s
;
dev_dbg
(
g_dev_ptr
,
"stopping all compressors, current channel %d, fvld = %d, file->f_pos = 0x%llx
\n
"
,
chn
,
fvld
,
file
->
f_pos
);
for
(
s
=
0
;
s
<
IMAGE_CHN_NUM
;
s
++
)
stop_compressor
(
s
);
dump_state
(
chn
);
}
break
;
/* end of debug code */
}
switch
(
offset
)
{
case
LSEEK_CIRC_FREE
:
...
...
@@ -448,7 +552,8 @@ loff_t circbuf_lseek(struct file *file, loff_t offset, int orig)
return
-
EOVERFLOW
;
}
len32
&=
FRAME_LENGTH_MASK
;
img_start
=
X393_BUFFSUB
(
last_image_chunk
+
CHUNK_SIZE
-
INSERTED_BYTES
(
len32
)
-
CCAM_MMAP_META
,
len32
);
//img_start = X393_BUFFSUB(last_image_chunk + CHUNK_SIZE - INSERTED_BYTES(len32) - CCAM_MMAP_META, len32);
img_start
=
get_image_start
(
last_image_chunk
,
len32
);
dev_dbg
(
g_dev_ptr
,
"calculated start address = 0x%x, length = 0x%x
\n
"
,
img_start
,
len32
);
if
(
circbuf_valid_ptr
(
img_start
,
&
fp
,
chn
)
<
0
)
return
-
EOVERFLOW
;
...
...
@@ -470,7 +575,8 @@ loff_t circbuf_lseek(struct file *file, loff_t offset, int orig)
return
-
EOVERFLOW
;
}
len32
&=
FRAME_LENGTH_MASK
;
img_start
=
X393_BUFFSUB
(
last_image_chunk
+
CHUNK_SIZE
-
INSERTED_BYTES
(
len32
)
-
CCAM_MMAP_META
,
len32
);
//img_start = X393_BUFFSUB(last_image_chunk + CHUNK_SIZE - INSERTED_BYTES(len32) - CCAM_MMAP_META, len32);
img_start
=
get_image_start
(
last_image_chunk
,
len32
);
dev_dbg
(
g_dev_ptr
,
"LSEEK_CIRC_PREV: calculated start address = 0x%x, length = 0x%x
\n
"
,
img_start
,
len32
);
// move file pointer only if previous frame valid
...
...
@@ -481,8 +587,11 @@ loff_t circbuf_lseek(struct file *file, loff_t offset, int orig)
break
;
case
LSEEK_CIRC_NEXT
:
dev_dbg
(
g_dev_ptr
,
"LSEEK_CIRC_NEXT: file->f_pos = 0x%lx, fvld = %d, fp->len32 = 0x%lx
\n
"
,
file
->
f_pos
,
fvld
,
fp
->
frame_length
);
if
(
fvld
<=
0
)
if
(
fvld
<=
0
)
{
return
-
EOVERFLOW
;
//! no frames after current
}
else
if
(
fvld
==
2
)
{
file
->
f_pos
+=
CHUNK_SIZE
;
}
// calculate the full length of current frame and advance file pointer by this value
padded_frame
=
fp
->
frame_length
+
INSERTED_BYTES
(
fp
->
frame_length
)
+
CHUNK_SIZE
+
CCAM_MMAP_META
;
file
->
f_pos
=
X393_BUFFADD
(
file
->
f_pos
,
padded_frame
);
// do it even if the next frame does not yet exist
...
...
@@ -507,7 +616,8 @@ loff_t circbuf_lseek(struct file *file, loff_t offset, int orig)
if
((
len32
&
MARKER_FF
)
!=
MARKER_FF
)
break
;
//! no frames before rp (==prev_p)
//! move rp to the previous frame
len32
&=
FRAME_LENGTH_MASK
;
img_start
=
X393_BUFFSUB
(
last_image_chunk
+
CHUNK_SIZE
-
INSERTED_BYTES
(
len32
)
-
CCAM_MMAP_META
,
len32
);
//img_start = X393_BUFFSUB(last_image_chunk + CHUNK_SIZE - INSERTED_BYTES(len32) - CCAM_MMAP_META, len32);
img_start
=
get_image_start
(
last_image_chunk
,
len32
);
dev_dbg
(
g_dev_ptr
,
"LSEEK_CIRC_FIRST or LSEEK_CIRC_SCND: calculated start address = 0x%x, length = 0x%x
\n
"
,
img_start
,
len32
);
rp
=
BYTE2DW
(
img_start
);
if
(
rp
>
prev_p
)
nz
--
;
// rolled through zero - make sure we'll not stuck in this loop forever
...
...
@@ -562,12 +672,17 @@ loff_t circbuf_lseek(struct file *file, loff_t offset, int orig)
* @param[in] off offset
* @return number of bytes read form \e buf
*/
unsigned
short
circbuf_quality
=
100
;
ssize_t
circbuf_write
(
struct
file
*
file
,
const
char
*
buf
,
size_t
count
,
loff_t
*
off
)
{
unsigned
long
p
;
unsigned
int
minor
=
MINOR
(
file
->
f_inode
->
i_rdev
);
unsigned
int
chn
=
minor_to_chn
(
minor
,
NULL
);
dev_dbg
(
g_dev_ptr
,
"minor = 0x%x, count = 0x%x, off = 0x%lx"
,
minor
,
count
,
off
);
int
i
;
int
ret
;
long
val
;
char
*
buf_copy
=
(
char
*
)
buf
;
dev_dbg
(
g_dev_ptr
,
"minor = 0x%x, count = 0x%x, off = 0x%llx"
,
minor
,
count
,
off
);
/* debug code follows*/
switch
(
buf
[
0
]
-
0x30
)
{
...
...
@@ -577,6 +692,24 @@ ssize_t circbuf_write(struct file *file, const char *buf, size_t count, loff_t *
case
1
:
camera_interrupts
(
1
);
break
;
case
3
:
buf_copy
[
count
-
1
]
=
0
;
ret
=
kstrtol
(
&
buf_copy
[
2
],
10
,
&
val
);
dev_dbg
(
g_dev_ptr
,
"ret: %d, buf[2]: %s
\n
"
,
ret
,
&
buf_copy
[
2
]);
if
(
count
>
2
&&
ret
==
0
)
{
if
(
val
>
0
&&
val
<=
100
)
{
circbuf_quality
=
val
;
dev_dbg
(
g_dev_ptr
,
"set quality %d
\n
"
,
circbuf_quality
);
}
}
else
{
dev_dbg
(
g_dev_ptr
,
"error, unable to process quality parameter
\n
"
);
}
break
;
case
4
:
for
(
i
=
0
;
i
<
IMAGE_CHN_NUM
;
i
++
)
stop_compressor
(
i
);
dump_state
(
chn
);
break
;
}
/* debug code end */
...
...
src/drivers/elphel/circbuf.h
View file @
3c64c8e0
...
...
@@ -42,4 +42,8 @@ struct circbuf_priv_t {
};
extern
struct
circbuf_priv_t
*
circbuf_priv_ptr
;
/* debug code follows */
extern
unsigned
short
circbuf_quality
;
/* end of debug code */
#endif
/* _CIRCBUF_H */
src/drivers/elphel/sensor_common.c
View file @
3c64c8e0
...
...
@@ -41,6 +41,8 @@
//#include <linux/of.h>
//#include <linux/of_device.h>
#include <asm/outercache.h>
#include <asm/cacheflush.h>
//#include <asm/system.h>
//#include <asm/byteorder.h> // endians
...
...
@@ -114,6 +116,10 @@ struct image_acq_pd_t {
int
minor
;
struct
jpeg_ptr_t
jpeg_ptr
[
IMAGE_CHN_NUM
];
};
/* debug code follows */
// jpeg_hw_wp is equal to hardware pointer, jpeg_wp will lag from jpeg_hw_wp by one frame
static
volatile
int
jpeg_hw_wp
[
IMAGE_CHN_NUM
];
/* end of debug code */
static
struct
image_acq_pd_t
image_acq_priv
;
...
...
@@ -242,10 +248,12 @@ DECLARE_TASKLET(tasklet_fpga, tasklet_fpga_function, 0); /// 0 - no arguments fo
*/
static
inline
int
updateIRQJPEG_wp
(
struct
jpeg_ptr_t
*
jptr
)
{
phys_addr_t
phys_addr
;
void
*
virt_addr
;
int
xferred
;
/// number of 32-byte chunks transferred since compressor was reset
x393_afimux_status_t
stat
=
x393_afimux0_status
(
jptr
->
chn_num
);
//
int circbuf_size = get_globalParam(G_CIRCBUFSIZE) >> 2;
int
circbuf_size
=
get_globalParam
(
G_CIRCBUFSIZE
);
int
circbuf_size
=
get_globalParam
(
G_CIRCBUFSIZE
)
>>
2
;
//
int circbuf_size = get_globalParam(G_CIRCBUFSIZE);
xferred
=
stat
.
offset256
-
jptr
->
fpga_cntr_prev
;
if
(
xferred
==
0
)
...
...
@@ -253,8 +261,27 @@ static inline int updateIRQJPEG_wp(struct jpeg_ptr_t *jptr)
jptr
->
flags
|=
SENS_FLAG_IRQ
;
jptr
->
fpga_cntr_prev
=
stat
.
offset256
;
// if (xferred < 0)
// xferred += (1 << 26);
// increment in 32 bit words
jptr
->
jpeg_wp
+=
(
xferred
<<
3
);
// jptr->jpeg_wp += (xferred << 3);
jptr
->
jpeg_wp
=
(
stat
.
offset256
<<
3
);
// if (jptr->jpeg_wp > circbuf_size)
// jptr->jpeg_wp -= circbuf_size;
// invalidate CPU L1 and L2 caches
phys_addr
=
circbuf_priv_ptr
[
jptr
->
chn_num
].
phys_addr
+
DW2BYTE
(
jptr
->
jpeg_wp
)
-
CHUNK_SIZE
;
virt_addr
=
circbuf_priv_ptr
[
jptr
->
chn_num
].
buf_ptr
+
jptr
->
jpeg_wp
-
INTERFRAME_PARAMS_SZ
;
outer_inv_range
(
phys_addr
,
phys_addr
+
(
CHUNK_SIZE
-
1
));
__cpuc_flush_dcache_area
(
virt_addr
,
CHUNK_SIZE
);
// printk(KERN_DEBUG "this channel start address: phys_addr = 0x%x; buf_ptr = 0x%x",
// circbuf_priv_ptr[jptr->chn_num].phys_addr, circbuf_priv_ptr[jptr->chn_num].buf_ptr);
// printk(KERN_DEBUG "invalidate cache for channel %d: phys_addr = 0x%x; virt_addr = 0x%x\n",
// jptr->chn_num, phys_addr, virt_addr);
// printk(KERN_DEBUG "\t\tcurrent parameters: offset256 = 0x%x, fpga_prev_cntr = 0x%x, jpeg_wp = 0x%x, xferred = 0x%x\n",
// stat.offset256, jptr->fpga_cntr_prev, jptr->jpeg_wp, xferred);
return
1
;
}
...
...
@@ -289,7 +316,8 @@ inline static void set_default_interframe(struct interframe_params_t *params)
params
->
width
=
2592
;
params
->
byrshift
=
3
;
params
->
color
=
0
;
params
->
quality2
=
100
;
params
->
quality2
=
circbuf_quality
;
//params->quality2 = 100;
}
/**
...
...
@@ -313,6 +341,8 @@ inline struct interframe_params_t* updateIRQ_interframe(struct jpeg_ptr_t *jptr)
// set_globalParam (0x306,get_globalParam (0x306)+1);
//#endif
dma_addr_t
phys_addr
;
void
*
virt_addr
;
struct
interframe_params_t
*
interframe
;
int
len_offset
=
X393_BUFFSUB
(
jptr
->
jpeg_wp
,
INTERFRAME_PARAMS_SZ
+
1
);
int
jpeg_len
=
circbuf_priv_ptr
[
jptr
->
chn_num
].
buf_ptr
[
len_offset
]
&
FRAME_LENGTH_MASK
;
...
...
@@ -328,6 +358,19 @@ inline struct interframe_params_t* updateIRQ_interframe(struct jpeg_ptr_t *jptr)
set_globalParam
(
G_FRAME_SIZE
,
jpeg_len
);
// invalidate CPU L1 and L2 caches
phys_addr
=
circbuf_priv_ptr
[
jptr
->
chn_num
].
phys_addr
+
DW2BYTE
(
frame_params_offset
);
//virt_addr = circbuf_priv_ptr[jptr->chn_num].buf_ptr + frame_params_offset;
virt_addr
=
interframe
;
__cpuc_flush_dcache_area
(
virt_addr
,
CHUNK_SIZE
);
outer_inv_range
(
phys_addr
,
phys_addr
+
(
CHUNK_SIZE
-
1
));
if
(
jptr
->
chn_num
==
0
)
{
printk
(
KERN_DEBUG
"this channel start address: phys_addr = 0x%x; buf_ptr = 0x%x"
,
circbuf_priv_ptr
[
jptr
->
chn_num
].
phys_addr
,
circbuf_priv_ptr
[
jptr
->
chn_num
].
buf_ptr
);
printk
(
KERN_DEBUG
"invalidate cache for channel %d: phys_addr = 0x%x; virt_addr = 0x%x
\n
"
,
jptr
->
chn_num
,
phys_addr
,
virt_addr
);
}
return
interframe
;
}
...
...
@@ -644,6 +687,9 @@ void reset_compressor(unsigned int chn)
image_acq_priv
.
jpeg_ptr
[
chn
].
jpeg_rp
=
0
;
image_acq_priv
.
jpeg_ptr
[
chn
].
fpga_cntr_prev
=
0
;
image_acq_priv
.
jpeg_ptr
[
chn
].
flags
=
0
;
/* debug code follows */
jpeg_hw_wp
[
chn
]
=
0
;
/* debug code end */
//update_irq_circbuf(jptr);
local_irq_restore
(
flags
);
}
...
...
src/include/elphel/c313a.h
View file @
3c64c8e0
...
...
@@ -1361,6 +1361,7 @@ struct p_names_t {
#define LSEEK_CIRC_WAIT 11
#define LSEEK_CIRC_FREE 12
#define LSEEK_CIRC_USED 13
#define LSEEK_CIRC_STOP_COMPRESSOR 14
#define LSEEK_HUFFMAN_DC0 1
#define LSEEK_HUFFMAN_AC0 2
...
...
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