Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
O
OpenXG-RAN
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
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
canghaiwuhen
OpenXG-RAN
Commits
02c8b38b
Commit
02c8b38b
authored
Aug 02, 2019
by
laurent
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
adding timestamp management
parent
5a7cbde5
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
70 additions
and
54 deletions
+70
-54
executables/main-fs6.c
executables/main-fs6.c
+61
-53
executables/split_headers.h
executables/split_headers.h
+2
-0
executables/transport_split.c
executables/transport_split.c
+5
-0
targets/RT/USER/lte-softmodem.c
targets/RT/USER/lte-softmodem.c
+2
-1
No files found.
executables/main-fs6.c
View file @
02c8b38b
...
...
@@ -21,7 +21,6 @@
static
UDPsock_t
sockFS6
;
void
prach_eNB_extract
(
uint8_t
*
bufferZone
,
int
bufSize
,
PHY_VARS_eNB
*
eNB
,
int
frame
,
int
subframe
)
{
//commonUDP_t *UDPheader=(commonUDP_t *) bufferZone;
fs6_ul_t
*
header
=
(
fs6_ul_t
*
)
commonUDPdata
(
bufferZone
);
if
(
is_prach_subframe
(
&
eNB
->
frame_parms
,
frame
,
subframe
)
<=
0
)
...
...
@@ -74,7 +73,6 @@ void prach_eNB_extract(uint8_t *bufferZone, int bufSize, PHY_VARS_eNB *eNB, int
}
void
prach_eNB_process
(
uint8_t
*
bufferZone
,
int
bufSize
,
PHY_VARS_eNB
*
eNB
,
RU_t
*
ru
,
int
frame
,
int
subframe
)
{
//commonUDP_t *UDPheader=(commonUDP_t *) bufferZone;
fs6_ul_t
*
header
=
(
fs6_ul_t
*
)
commonUDPdata
(
bufferZone
);
uint16_t
*
max_preamble
=
header
->
max_preamble
;
uint16_t
*
max_preamble_energy
=
header
->
max_preamble_energy
;
...
...
@@ -438,16 +436,14 @@ void phy_procedures_eNB_uespec_RX_process(uint8_t *bufferZone, int nbBlocks, PHY
}
void
phy_procedures_eNB_TX_process
(
uint8_t
*
bufferZone
,
int
nbBlocks
,
PHY_VARS_eNB
*
eNB
,
L1_rxtx_proc_t
*
proc
,
int
do_meas
)
{
//commonUDP_t *UDPheader=(commonUDP_t *) bufferZone;
fs6_dl_t
*
header
=
(
fs6_dl_t
*
)
commonUDPdata
(
bufferZone
);
// TBD: read frame&subframe from received data
int
frame
=
proc
->
frame_tx
=
h
eader
->
frame
;
int
subframe
=
proc
->
subframe_tx
=
h
eader
->
subframe
;
int
frame
=
proc
->
frame_tx
=
h
DL
(
bufferZone
)
->
frame
;
int
subframe
=
proc
->
subframe_tx
=
h
DL
(
bufferZone
)
->
subframe
;
LTE_DL_FRAME_PARMS
*
fp
=&
eNB
->
frame_parms
;
//LTE_UL_eNB_HARQ_t *ulsch_harq;
eNB
->
pdcch_vars
[
subframe
&
1
].
num_pdcch_symbols
=
h
eader
->
num_pdcch_symbols
;
eNB
->
pdcch_vars
[
subframe
&
1
].
num_dci
=
h
eader
->
num_dci
;
uint8_t
num_mdci
=
eNB
->
mpdcch_vars
[
subframe
&
1
].
num_dci
=
h
eader
->
num_mdci
;
eNB
->
pdcch_vars
[
subframe
&
1
].
num_pdcch_symbols
=
h
DL
(
bufferZone
)
->
num_pdcch_symbols
;
eNB
->
pdcch_vars
[
subframe
&
1
].
num_dci
=
h
DL
(
bufferZone
)
->
num_dci
;
uint8_t
num_mdci
=
eNB
->
mpdcch_vars
[
subframe
&
1
].
num_dci
=
h
DL
(
bufferZone
)
->
num_mdci
;
// Remove all scheduled DL, we will populate from the CU sending
for
(
int
UE_id
=
0
;
UE_id
<
NUMBER_OF_UE_MAX
;
UE_id
++
)
{
...
...
@@ -465,16 +461,19 @@ void phy_procedures_eNB_TX_process(uint8_t *bufferZone, int nbBlocks, PHY_VARS_e
void
*
bufPtr
=
bufferZone
;
for
(
int
i
=
0
;
i
<
nbBlocks
;
i
++
)
{
//nbBlocks is the actual received blocks
fs6_dl_uespec_t
*
dlPtr
=
(
fs6_dl_uespec_t
*
)(
commonUDPdata
(
bufPtr
)
+
sizeof
(
fs6_dl_t
));
if
(
((
commonUDP_t
*
)
bufPtr
)
->
contentBytes
>=
sizeof
(
fs6_dl_t
)
+
sizeof
(
fs6_dl_uespec_t
)
)
{
fs6_dl_uespec_t
*
dlPtr
=
(
fs6_dl_uespec_t
*
)(
commonUDPdata
(
bufPtr
)
+
sizeof
(
fs6_dl_t
));
#ifdef PHY_TX_THREAD
eNB
->
dlsch
[
dlPtr
->
UE_id
][
0
]
->
active
[
subframe
]
=
1
;
eNB
->
dlsch
[
dlPtr
->
UE_id
][
0
]
->
active
[
subframe
]
=
1
;
#else
eNB
->
dlsch
[
dlPtr
->
UE_id
][
0
]
->
active
=
1
;
eNB
->
dlsch
[
dlPtr
->
UE_id
][
0
]
->
active
=
1
;
#endif
eNB
->
dlsch
[
dlPtr
->
UE_id
][
0
]
->
harq_ids
[
frame
%
2
][
subframe
]
=
dlPtr
->
harq_pid
;
eNB
->
dlsch
[
dlPtr
->
UE_id
][
0
]
->
rnti
=
dlPtr
->
rnti
;
memcpy
(
eNB
->
dlsch
[
dlPtr
->
UE_id
][
0
]
->
harq_processes
[
dlPtr
->
harq_pid
]
->
e
,
commonUDPdata
(((
uint8_t
*
)
dlPtr
)
+
sizeof
(
fs6_dl_t
)),
dlPtr
->
dataLen
);
eNB
->
dlsch
[
dlPtr
->
UE_id
][
0
]
->
harq_ids
[
frame
%
2
][
subframe
]
=
dlPtr
->
harq_pid
;
eNB
->
dlsch
[
dlPtr
->
UE_id
][
0
]
->
rnti
=
dlPtr
->
rnti
;
memcpy
(
eNB
->
dlsch
[
dlPtr
->
UE_id
][
0
]
->
harq_processes
[
dlPtr
->
harq_pid
]
->
e
,
commonUDPdata
(((
uint8_t
*
)
dlPtr
)
+
sizeof
(
fs6_dl_t
)),
dlPtr
->
dataLen
);
}
bufPtr
+=
alignedSize
(
bufPtr
);
}
...
...
@@ -505,17 +504,17 @@ void phy_procedures_eNB_TX_process(uint8_t *bufferZone, int nbBlocks, PHY_VARS_e
}
if
(
NFAPI_MODE
==
NFAPI_MONOLITHIC
||
NFAPI_MODE
==
NFAPI_MODE_PNF
)
{
for
(
int
i
=
0
;
i
<
h
eader
->
num_dci
;
i
++
)
eNB
->
pdcch_vars
[
subframe
&
1
].
dci_alloc
[
i
]
=
h
eader
->
dci_alloc
[
i
];
for
(
int
i
=
0
;
i
<
h
DL
(
bufferZone
)
->
num_dci
;
i
++
)
eNB
->
pdcch_vars
[
subframe
&
1
].
dci_alloc
[
i
]
=
h
DL
(
bufferZone
)
->
dci_alloc
[
i
];
generate_dci_top
(
h
eader
->
num_pdcch_symbols
,
h
eader
->
num_dci
,
generate_dci_top
(
h
DL
(
bufferZone
)
->
num_pdcch_symbols
,
h
DL
(
bufferZone
)
->
num_dci
,
&
eNB
->
pdcch_vars
[
subframe
&
1
].
dci_alloc
[
0
],
0
,
h
eader
->
amp
,
h
DL
(
bufferZone
)
->
amp
,
fp
,
eNB
->
common_vars
.
txdataF
,
h
eader
->
subframe
);
h
DL
(
bufferZone
)
->
subframe
);
if
(
num_mdci
>
0
)
{
LOG_D
(
PHY
,
"[eNB %"
PRIu8
"] Frame %d, subframe %d: Calling generate_mdci_top (mpdcch) (num_dci %"
PRIu8
")
\n
"
,
eNB
->
Mod_id
,
frame
,
subframe
,
num_mdci
);
...
...
@@ -543,7 +542,7 @@ void phy_procedures_eNB_TX_process(uint8_t *bufferZone, int nbBlocks, PHY_VARS_e
}
}
eNB
->
phich_vars
[
subframe
&
1
]
=
h
eader
->
phich_vars
;
eNB
->
phich_vars
[
subframe
&
1
]
=
h
DL
(
bufferZone
)
->
phich_vars
;
generate_phich_top
(
eNB
,
proc
,
AMP
);
...
...
@@ -576,7 +575,6 @@ void appendFs6DLUE(uint8_t *bufferZone, int UE_id, int8_t harq_pid, uint16_t rnt
}
void
phy_procedures_eNB_TX_extract
(
uint8_t
*
bufferZone
,
PHY_VARS_eNB
*
eNB
,
L1_rxtx_proc_t
*
proc
,
int
do_meas
,
uint8_t
*
buf
,
int
bufSize
)
{
fs6_dl_t
*
header
=
(
fs6_dl_t
*
)
commonUDPdata
(
bufferZone
);
int
frame
=
proc
->
frame_tx
;
int
subframe
=
proc
->
subframe_tx
;
LTE_DL_FRAME_PARMS
*
fp
=&
eNB
->
frame_parms
;
...
...
@@ -584,12 +582,12 @@ void phy_procedures_eNB_TX_extract(uint8_t *bufferZone, PHY_VARS_eNB *eNB, L1_rx
if
((
fp
->
frame_type
==
TDD
)
&&
(
subframe_select
(
fp
,
subframe
)
==
SF_UL
))
return
;
h
eader
->
frame
=
frame
;
h
eader
->
subframe
=
subframe
;
h
DL
(
bufferZone
)
->
frame
=
frame
;
h
DL
(
bufferZone
)
->
subframe
=
subframe
;
// clear existing ulsch dci allocations before applying info from MAC (this is table
// returns -1 (or 255) if there is no ul (tdd cases)
h
eader
->
ul_subframe
=
pdcch_alloc2ul_subframe
(
fp
,
subframe
);
h
eader
->
ul_frame
=
pdcch_alloc2ul_frame
(
fp
,
frame
,
subframe
);
h
DL
(
bufferZone
)
->
ul_subframe
=
pdcch_alloc2ul_subframe
(
fp
,
subframe
);
h
DL
(
bufferZone
)
->
ul_frame
=
pdcch_alloc2ul_frame
(
fp
,
frame
,
subframe
);
// clear previous allocation information for all UEs
for
(
int
i
=
0
;
i
<
NUMBER_OF_UE_MAX
;
i
++
)
{
...
...
@@ -600,7 +598,7 @@ void phy_procedures_eNB_TX_extract(uint8_t *bufferZone, PHY_VARS_eNB *eNB, L1_rx
/* TODO: check the following test - in the meantime it is put back as it was before */
//if ((ul_subframe < 10)&&
// (subframe_select(fp,ul_subframe)==SF_UL)) { // This means that there is a potential UL subframe that will be scheduled here
if
(
h
eader
->
ul_subframe
<
10
)
{
// This means that there is a potential UL subframe that will be schedul
ed here
if
(
h
DL
(
bufferZone
)
->
ul_subframe
<
10
)
{
// This means that there is a potential UL subframe that will be scheduly
ed here
for
(
int
i
=
0
;
i
<
NUMBER_OF_UE_MAX
;
i
++
)
{
int
harq_pid
;
...
...
@@ -608,7 +606,8 @@ void phy_procedures_eNB_TX_extract(uint8_t *bufferZone, PHY_VARS_eNB *eNB, L1_rx
// LTE-M case
harq_pid
=
0
;
else
header
->
UE_ul_active
[
i
]
=
harq_pid
=
subframe2harq_pid
(
fp
,
header
->
ul_frame
,
header
->
ul_subframe
);
hDL
(
bufferZone
)
->
UE_ul_active
[
i
]
=
harq_pid
=
subframe2harq_pid
(
fp
,
hDL
(
bufferZone
)
->
ul_frame
,
hDL
(
bufferZone
)
->
ul_subframe
);
if
(
eNB
->
ulsch
[
i
])
{
//LTE_UL_eNB_HARQ_t *ulsch_harq = eNB->ulsch[i]->harq_processes[harq_pid];
...
...
@@ -636,9 +635,9 @@ void phy_procedures_eNB_TX_extract(uint8_t *bufferZone, PHY_VARS_eNB *eNB, L1_rx
header->previous_n_DMRS =
ulsch_harq->previous_n_DMRS = ulsch_harq->n_DMRS;
*/
h
eader
->
UE_ul_active
[
i
]
=
harq_pid
;
h
DL
(
bufferZone
)
->
UE_ul_active
[
i
]
=
harq_pid
;
}
else
h
eader
->
UE_ul_active
[
i
]
=
-
1
;
h
DL
(
bufferZone
)
->
UE_ul_active
[
i
]
=
-
1
;
}
}
...
...
@@ -648,10 +647,10 @@ void phy_procedures_eNB_TX_extract(uint8_t *bufferZone, PHY_VARS_eNB *eNB, L1_rx
LOG_D
(
PHY
,
"num_pdcch_symbols %"
PRIu8
",number dci %"
PRIu8
"
\n
"
,
num_pdcch_symbols
,
num_dci
);
if
(
NFAPI_MODE
==
NFAPI_MONOLITHIC
||
NFAPI_MODE
==
NFAPI_MODE_PNF
)
{
h
eader
->
num_pdcch_symbols
=
num_pdcch_symbols
;
h
eader
->
num_dci
=
num_dci
;
h
eader
->
num_mdci
=
num_mdci
;
h
eader
->
amp
=
AMP
;
h
DL
(
bufferZone
)
->
num_pdcch_symbols
=
num_pdcch_symbols
;
h
DL
(
bufferZone
)
->
num_dci
=
num_dci
;
h
DL
(
bufferZone
)
->
num_mdci
=
num_mdci
;
h
DL
(
bufferZone
)
->
amp
=
AMP
;
}
if
(
do_meas
==
1
)
stop_meas
(
&
eNB
->
dlsch_common_and_dci
);
...
...
@@ -733,7 +732,7 @@ void phy_procedures_eNB_TX_extract(uint8_t *bufferZone, PHY_VARS_eNB *eNB, L1_rx
}
}
h
eader
->
phich_vars
=
eNB
->
phich_vars
[
subframe
&
1
];
h
DL
(
bufferZone
)
->
phich_vars
=
eNB
->
phich_vars
[
subframe
&
1
];
if
(
do_meas
==
1
)
stop_meas
(
&
eNB
->
dlsch_ue_specific
);
...
...
@@ -743,10 +742,15 @@ void phy_procedures_eNB_TX_extract(uint8_t *bufferZone, PHY_VARS_eNB *eNB, L1_rx
}
void
DL_du_fs6
(
RU_t
*
ru
,
int
frame
,
int
subframe
,
uint64_t
TS
)
{
//RU_proc_t *ru_proc=&ru->proc;
RU_proc_t
*
ru_proc
=&
ru
->
proc
;
for
(
int
i
=
0
;
i
<
ru
->
num_eNB
;
i
++
)
{
initBufferZone
(
bufferZone
);
int
nb_blocks
=
receiveSubFrame
(
&
sockFS6
,
TS
,
bufferZone
,
sizeof
(
bufferZone
)
);
L1_rxtx_proc_t
*
L1_proc
=
&
ru
->
eNB_list
[
i
]
->
proc
.
L1_proc_tx
;
L1_proc
->
timestamp_tx
=
hUDP
(
bufferZone
)
->
timestamp
;
L1_proc
->
frame_tx
=
hDL
(
bufferZone
)
->
frame
;
L1_proc
->
subframe_tx
=
hDL
(
bufferZone
)
->
subframe
;
if
(
nb_blocks
>
0
)
phy_procedures_eNB_TX_process
(
bufferZone
,
nb_blocks
,
ru
->
eNB_list
[
i
],
&
ru
->
eNB_list
[
i
]
->
proc
.
L1_proc
,
1
);
...
...
@@ -754,12 +758,11 @@ void DL_du_fs6(RU_t *ru, int frame, int subframe, uint64_t TS) {
LOG_E
(
PHY
,
"DL not received for subframe: %d
\n
"
,
subframe
);
}
/* Fixme: datamodel issue: a ru is supposed to be connected to several eNB
L1_rxtx_proc_t * L1_proc = &proc->L1_proc;
ru_proc->timestamp_tx = L1_proc->timestamp_tx;
ru_proc->subframe_tx = L1_proc->subframe_tx;
ru_proc->frame_tx = L1_proc->frame_tx;
*/
/* OAI data model is wrong: hardcode link ru to enb index 0*/
L1_rxtx_proc_t
*
L1_proc
=
&
ru
->
eNB_list
[
0
]
->
proc
.
L1_proc_tx
;
ru_proc
->
timestamp_tx
=
L1_proc
->
timestamp_tx
;
ru_proc
->
subframe_tx
=
L1_proc
->
subframe_tx
;
ru_proc
->
frame_tx
=
L1_proc
->
frame_tx
;
feptx_prec
(
ru
);
feptx_ofdm
(
ru
);
tx_rf
(
ru
);
...
...
@@ -787,6 +790,9 @@ void UL_du_fs6(RU_t *ru, int frame, int subframe) {
}
initBufferZone
(
bufferZone
);
hDL
(
bufferZone
)
->
frame
=
frame
;
hDL
(
bufferZone
)
->
subframe
=
subframe
;
hUDP
(
bufferZone
)
->
timestamp
=
ru
->
proc
.
timestamp_rx
;
prach_eNB_extract
(
bufferZone
,
FS6_BUF_SIZE
,
eNB
,
proc
->
frame_rx
,
proc
->
subframe_rx
);
if
(
NFAPI_MODE
==
NFAPI_MONOLITHIC
||
NFAPI_MODE
==
NFAPI_MODE_PNF
)
{
...
...
@@ -811,12 +817,14 @@ void DL_cu_fs6(RU_t *ru,int frame, int subframe) {
pthread_mutex_unlock
(
&
eNB
->
UL_INFO_mutex
);
initBufferZone
(
bufferZone
);
phy_procedures_eNB_TX_extract
(
bufferZone
,
eNB
,
&
proc
->
L1_proc
,
1
,
bufferZone
,
FS6_BUF_SIZE
);
commonUDP_t
*
firstHeader
=
(
commonUDP_t
*
)
bufferZone
;
if
(
firstHeader
->
nbBlocks
==
0
)
{
firstHeader
->
nbBlocks
=
1
;
// We have to send the signaling, even is there is no user plan data (no UE)
firstHeader
->
blockID
=
0
;
firstHeader
->
contentBytes
=
sizeof
(
fs6_dl_t
);
hUDP
(
bufferZone
)
->
timestamp
=
proc
->
L1_proc
.
timestamp_tx
;
hDL
(
bufferZone
)
->
frame
=
frame
;
hDL
(
bufferZone
)
->
subframe
=
subframe
;
if
(
hUDP
(
bufferZone
)
->
nbBlocks
==
0
)
{
hUDP
(
bufferZone
)
->
nbBlocks
=
1
;
// We have to send the signaling, even is there is no user plan data (no UE)
hUDP
(
bufferZone
)
->
blockID
=
0
;
hUDP
(
bufferZone
)
->
contentBytes
=
sizeof
(
fs6_dl_t
);
}
sendSubFrame
(
&
sockFS6
,
bufferZone
,
sizeof
(
fs6_dl_t
)
);
...
...
@@ -824,7 +832,6 @@ void DL_cu_fs6(RU_t *ru,int frame, int subframe) {
void
UL_cu_fs6
(
RU_t
*
ru
,
int
frame
,
int
subframe
,
uint64_t
TS
)
{
initBufferZone
(
bufferZone
);
commonUDP_t
*
UDPheader
=
(
commonUDP_t
*
)
bufferZone
;
int
nb_blocks
=
receiveSubFrame
(
&
sockFS6
,
TS
,
bufferZone
,
sizeof
(
bufferZone
)
);
if
(
nb_blocks
==
0
)
{
...
...
@@ -832,12 +839,13 @@ void UL_cu_fs6(RU_t *ru,int frame, int subframe, uint64_t TS) {
return
;
}
if
(
nb_blocks
!=
UDPheader
->
nbBlocks
)
LOG_W
(
PHY
,
"received %d blocks for %d expected
\n
"
,
nb_blocks
,
UDPheader
->
nbBlocks
);
if
(
nb_blocks
!=
hUDP
(
bufferZone
)
->
nbBlocks
)
LOG_W
(
PHY
,
"received %d blocks for %d expected
\n
"
,
nb_blocks
,
hUDP
(
bufferZone
)
->
nbBlocks
);
// Fixme: datamodel issue
PHY_VARS_eNB
*
eNB
=
RC
.
eNB
[
0
][
0
];
L1_proc_t
*
proc
=
&
eNB
->
proc
;
proc
->
timestamp_rx
=
hUDP
(
bufferZone
)
->
timestamp
;
prach_eNB_process
(
bufferZone
,
sizeof
(
bufferZone
),
eNB
,
NULL
,
proc
->
frame_rx
,
proc
->
subframe_rx
);
release_UE_in_freeList
(
eNB
->
Mod_id
);
...
...
executables/split_headers.h
View file @
02c8b38b
...
...
@@ -74,6 +74,8 @@ int sendSubFrame(UDPsock_t *sock, void *bufferZone, ssize_t secondHeaderSize);
uint8_t xBuf[FS6_BUF_SIZE];\
((commonUDP_t *)xBuf)->nbBlocks=0;
#define hUDP(xBuf) ((commonUDP_t *)xBuf)
#define hDL(xBuf) (((fs6_dl_t*)((commonUDP_t *)xBuf)+1))
inline
size_t
alignedSize
(
uint8_t
*
ptr
)
{
commonUDP_t
*
header
=
(
commonUDP_t
*
)
ptr
;
...
...
executables/transport_split.c
View file @
02c8b38b
...
...
@@ -104,6 +104,11 @@ int sendSubFrame(UDPsock_t *sock, void *bufferZone, ssize_t secondHeaderSize) {
int
nbBlocks
=
UDPheader
->
nbBlocks
;
int
blockId
=
0
;
if
(
nbBlocks
<=
0
)
{
LOG_E
(
PHY
,
"FS6: can't send blocks: %d
\n
"
,
nbBlocks
);
return
0
;
}
do
{
if
(
blockId
>
0
)
{
commonUDP_t
*
currentHeader
=
(
commonUDP_t
*
)
bufferZone
;
...
...
targets/RT/USER/lte-softmodem.c
View file @
02c8b38b
...
...
@@ -593,7 +593,8 @@ int main( int argc, char **argv ) {
/* initializes PDCP and sets correct RLC Request/PDCP Indication callbacks
* for monolithic/F1 modes */
init_pdcp
();
if
(
getenv
(
"fs6"
)
==
NULL
||
strncasecmp
(
getenv
(
"fs6"
),
"du"
,
2
)
!=
0
)
init_pdcp
();
if
(
create_tasks
(
1
)
<
0
)
{
printf
(
"cannot create ITTI tasks
\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