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
wangjie
OpenXG-RAN
Commits
f3b19c04
Commit
f3b19c04
authored
Sep 30, 2019
by
zhenghuangkun
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/trx_parallel_optimization' into develop
parents
c0752bec
22be6eb5
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
170 additions
and
162 deletions
+170
-162
openair1/PHY/LTE_TRANSPORT/dci_tools.c
openair1/PHY/LTE_TRANSPORT/dci_tools.c
+2
-74
openair1/PHY/LTE_TRANSPORT/dlsch_coding.c
openair1/PHY/LTE_TRANSPORT/dlsch_coding.c
+0
-5
openair1/PHY/LTE_TRANSPORT/transport_eNB.h
openair1/PHY/LTE_TRANSPORT/transport_eNB.h
+0
-4
openair1/SCHED/fapi_l1.c
openair1/SCHED/fapi_l1.c
+31
-20
openair1/SCHED/phy_procedures_lte_eNb.c
openair1/SCHED/phy_procedures_lte_eNb.c
+0
-12
openair2/ENB_APP/enb_config.c
openair2/ENB_APP/enb_config.c
+1
-1
openair2/LAYER2/MAC/eNB_scheduler_fairRR.c
openair2/LAYER2/MAC/eNB_scheduler_fairRR.c
+0
-15
openair3/GTPV1-U/gtpv1u_eNB.c
openair3/GTPV1-U/gtpv1u_eNB.c
+1
-1
targets/RT/USER/lte-enb.c
targets/RT/USER/lte-enb.c
+95
-10
targets/RT/USER/lte-ru.c
targets/RT/USER/lte-ru.c
+40
-20
No files found.
openair1/PHY/LTE_TRANSPORT/dci_tools.c
View file @
f3b19c04
...
...
@@ -328,12 +328,7 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
}
dlsch0_harq
->
ndi
=
rel8
->
new_data_indicator_1
;
#ifdef PHY_TX_THREAD
dlsch0
->
active
[
subframe
]
=
1
;
#else
dlsch0
->
active
=
1
;
#endif
if
(
rel8
->
rnti_type
==
2
)
dlsch0_harq
->
round
=
0
;
...
...
@@ -515,11 +510,7 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
dlsch0_harq
->
Qm
=
2
;
dlsch0_harq
->
TBS
=
TBStable
[
I_mcs
][
NPRB
-
1
];
dlsch0
->
harq_ids
[
frame
%
2
][
subframe
]
=
rel8
->
harq_process
;
#ifdef PHY_TX_THREAD
dlsch0
->
active
[
subframe
]
=
1
;
#else
dlsch0
->
active
=
1
;
#endif
dlsch0
->
rnti
=
rel8
->
rnti
;
//dlsch0->harq_ids[subframe] = rel8->harq_process;
...
...
@@ -534,11 +525,7 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
case
NFAPI_DL_DCI_FORMAT_1
:
dci_alloc
->
format
=
format1
;
#ifdef PHY_TX_THREAD
dlsch0
->
active
[
subframe
]
=
1
;
#else
dlsch0
->
active
=
1
;
#endif
LOG_D
(
PHY
,
"SFN/SF:%04d%d proc:TX:SFN/SF:%04d%d: Programming DLSCH for Format 1 DCI, harq_pid %d
\n
"
,
frame
,
subframe
,
proc
->
frame_tx
,
subframe
,
rel8
->
harq_process
);
...
...
@@ -682,11 +669,7 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
dlsch0_harq
->
dl_power_off
=
1
;
#ifdef PHY_TX_THREAD
dlsch0
->
active
[
subframe
]
=
1
;
#else
dlsch0
->
active
=
1
;
#endif
...
...
@@ -893,32 +876,19 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
// assume both TBs are active
dlsch0_harq
->
Nl
=
1
;
dlsch1_harq
->
Nl
=
1
;
#ifdef PHY_TX_THREAD
dlsch0
->
active
[
subframe
]
=
1
;
dlsch1
->
active
[
subframe
]
=
1
;
#else
dlsch0
->
active
=
1
;
dlsch1
->
active
=
1
;
#endif
dlsch0
->
harq_mask
|=
(
1
<<
rel8
->
harq_process
);
dlsch1
->
harq_mask
|=
(
1
<<
rel8
->
harq_process
);
// check if either TB is disabled (see 36-213 V11.3 Section )
if
((
dlsch0_harq
->
rvidx
==
1
)
&&
(
dlsch0_harq
->
mcs
==
0
))
{
#ifdef PHY_TX_THREAD
dlsch0
->
active
[
subframe
]
=
0
;
#else
dlsch0
->
active
=
0
;
#endif
dlsch0
->
harq_mask
&=
~
(
1
<<
rel8
->
harq_process
);
}
if
((
dlsch1_harq
->
rvidx
==
1
)
&&
(
dlsch1_harq
->
mcs
==
0
))
{
#ifdef PHY_TX_THREAD
dlsch1
->
active
[
subframe
]
=
0
;
#else
dlsch1
->
active
=
0
;
#endif
dlsch1
->
harq_mask
&=
~
(
1
<<
rel8
->
harq_process
);
}
// dlsch0_harq->dl_power_off = 0;
...
...
@@ -929,11 +899,7 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
dlsch0_harq
->
TBS
=
TBStable
[
get_I_TBS
(
dlsch0_harq
->
mcs
)][
dlsch0_harq
->
nb_rb
-
1
];
dlsch1_harq
->
TBS
=
TBStable
[
get_I_TBS
(
dlsch1_harq
->
mcs
)][
dlsch0_harq
->
nb_rb
-
1
];
#ifdef PHY_TX_THREAD
if
((
dlsch0
->
active
[
subframe
]
==
1
)
&&
(
dlsch1
->
active
[
subframe
]
==
1
))
{
#else
if
((
dlsch0
->
active
==
1
)
&&
(
dlsch1
->
active
==
1
))
{
#endif
dlsch0_harq
->
mimo_mode
=
LARGE_CDD
;
dlsch1_harq
->
mimo_mode
=
LARGE_CDD
;
dlsch0_harq
->
dl_power_off
=
1
;
...
...
@@ -943,11 +909,7 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
dlsch1_harq
->
mimo_mode
=
ALAMOUTI
;
}
}
else
if
(
fp
->
nb_antenna_ports_eNB
==
4
)
{
// 4 antenna case
#ifdef PHY_TX_THREAD
if
((
dlsch0
->
active
[
subframe
]
==
1
)
&&
(
dlsch1
->
active
[
subframe
]
==
1
))
{
#else
if
((
dlsch0
->
active
==
1
)
&&
(
dlsch1
->
active
==
1
))
{
#endif
switch
(
rel8
->
precoding_information
)
{
case
0
:
// one layer per transport block
dlsch0_harq
->
mimo_mode
=
LARGE_CDD
;
...
...
@@ -987,11 +949,7 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
LOG_E
(
PHY
,
"Illegal value (3) for TPMI in Format 2A DCI
\n
"
);
break
;
}
#ifdef PHY_TX_THREAD
}
else
if
(
dlsch0
->
active
[
subframe
]
==
1
)
{
#else
}
else
if
(
dlsch0
->
active
==
1
)
{
#endif
switch
(
rel8
->
precoding_information
)
{
case
0
:
// one layer per transport block
dlsch0_harq
->
mimo_mode
=
ALAMOUTI
;
...
...
@@ -1011,11 +969,7 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
LOG_E
(
PHY
,
"Illegal value %d for TPMI in Format 2A DCI with one transport block enabled
\n
"
,
rel8
->
precoding_information
);
break
;
}
#ifdef PHY_TX_THREAD
}
else
if
(
dlsch1
->
active
[
subframe
]
==
1
)
{
#else
}
else
if
(
dlsch1
->
active
==
1
)
{
#endif
switch
(
rel8
->
precoding_information
)
{
case
0
:
// one layer per transport block
dlsch0_harq
->
mimo_mode
=
ALAMOUTI
;
...
...
@@ -1041,18 +995,10 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
}
// reset HARQ process if this is the first transmission
#ifdef PHY_TX_THREAD
if
((
dlsch0
->
active
[
subframe
]
==
1
)
&&
(
dlsch0_harq
->
round
==
0
))
#else
if
((
dlsch0
->
active
==
1
)
&&
(
dlsch0_harq
->
round
==
0
))
#endif
dlsch0_harq
->
status
=
ACTIVE
;
#ifdef PHY_TX_THREAD
if
((
dlsch1
->
active
[
subframe
]
==
1
)
&&
(
dlsch1_harq
->
round
==
0
))
#else
if
((
dlsch1
->
active
==
1
)
&&
(
dlsch1_harq
->
round
==
0
))
#endif
dlsch1_harq
->
status
=
ACTIVE
;
dlsch0
->
rnti
=
rel8
->
rnti
;
...
...
@@ -1226,13 +1172,8 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
printf
(
"RV0 = %d, RV1 = %d. MCS0 = %d, MCS1=%d
\n
"
,
rel8
->
redundancy_version_1
,
rel8
->
redundancy_version_2
,
rel8
->
mcs_1
,
rel8
->
mcs_2
);
#endif
if
(
TB0_active
&&
TB1_active
&&
rel8
->
transport_block_to_codeword_swap_flag
==
0
)
{
#ifdef PHY_TX_THREAD
dlsch0
->
active
[
subframe
]
=
1
;
dlsch1
->
active
[
subframe
]
=
1
;
#else
dlsch0
->
active
=
1
;
dlsch1
->
active
=
1
;
#endif
dlsch0
->
harq_mask
|=
(
1
<<
rel8
->
harq_process
);
dlsch1
->
harq_mask
|=
(
1
<<
rel8
->
harq_process
);
dlsch0_harq
=
dlsch0
->
harq_processes
[
rel8
->
harq_process
];
...
...
@@ -1253,13 +1194,8 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
}
else
if
(
TB0_active
&&
TB1_active
&&
rel8
->
transport_block_to_codeword_swap_flag
==
1
)
{
dlsch0
=
eNB
->
dlsch
[
UE_id
][
1
];
dlsch1
=
eNB
->
dlsch
[
UE_id
][
0
];
#ifdef PHY_TX_THREAD
dlsch0
->
active
[
subframe
]
=
1
;
dlsch1
->
active
[
subframe
]
=
1
;
#else
dlsch0
->
active
=
1
;
dlsch1
->
active
=
1
;
#endif
dlsch0
->
harq_mask
|=
(
1
<<
rel8
->
harq_process
);
dlsch1
->
harq_mask
|=
(
1
<<
rel8
->
harq_process
);
...
...
@@ -1277,11 +1213,7 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
dlsch1_harq
->
codeword
=
0
;
}
else
if
(
TB0_active
&&
(
TB1_active
==
0
))
{
#ifdef PHY_TX_THREAD
dlsch0
->
active
[
subframe
]
=
1
;
#else
dlsch0
->
active
=
1
;
#endif
dlsch0
->
harq_mask
|=
(
1
<<
rel8
->
harq_process
);
dlsch0_harq
=
dlsch0
->
harq_processes
[
rel8
->
harq_process
];
dlsch0_harq
->
mcs
=
rel8
->
mcs_1
;
...
...
@@ -1296,11 +1228,7 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
#endif
}
else
if
((
TB0_active
==
0
)
&&
TB1_active
)
{
#ifdef PHY_TX_THREAD
dlsch1
->
active
[
subframe
]
=
1
;
#else
dlsch1
->
active
=
1
;
#endif
dlsch1
->
harq_mask
|=
(
1
<<
rel8
->
harq_process
);
dlsch1_harq
=
dlsch1
->
harq_processes
[
rel8
->
harq_process
];
dlsch1_harq
->
mcs
=
rel8
->
mcs_2
;
...
...
@@ -1503,7 +1431,7 @@ void fill_dci_and_dlsch(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_proc_t
#endif
//printf("DCI %d.%d rnti %d harq %d TBS %d\n", frame, subframe, rel8->rnti, rel8->harq_process, dlsch0_harq->TBS);
#if T_TRACER
if
(
dlsch0
->
active
)
if
(
dlsch0
->
active
[
subframe
]
)
T
(
T_ENB_PHY_DLSCH_UE_DCI
,
T_INT
(
0
),
T_INT
(
frame
),
T_INT
(
subframe
),
T_INT
(
rel8
->
rnti
),
T_INT
(
rel8
->
dci_format
),
T_INT
(
rel8
->
harq_process
),
T_INT
(
rel8
->
mcs_1
),
T_INT
(
dlsch0_harq
->
TBS
));
...
...
@@ -1772,7 +1700,7 @@ void fill_mdci_and_dlsch(PHY_VARS_eNB *eNB,L1_rxtx_proc_t *proc,mDCI_ALLOC_t *dc
else
AssertFatal
(
1
==
0
,
"Don't know how to set TBS (TPC %d)
\n
"
,
rel13
->
tpc
);
LOG_D
(
PHY
,
"fill_mdci_and_dlsch : TBS = %d(%d) %p, %x
\n
"
,
dlsch0_harq
->
TBS
,
dlsch0_harq
->
mcs
,
dlsch0
,
rel13
->
rnti
);
}
dlsch0
->
active
=
1
;
dlsch0
->
active
[
subframe
]
=
1
;
dlsch0
->
harq_mask
|=
(
1
<<
rel13
->
harq_process
);
dlsch0_harq
->
frame
=
(
subframe
>=
8
)
?
((
frame
+
1
)
&
1023
)
:
frame
;
...
...
openair1/PHY/LTE_TRANSPORT/dlsch_coding.c
View file @
f3b19c04
...
...
@@ -227,14 +227,9 @@ void clean_eNb_dlsch(LTE_eNB_DLSCH_t *dlsch) {
if
(
dlsch
)
{
Mdlharq
=
dlsch
->
Mdlharq
;
dlsch
->
rnti
=
0
;
#ifdef PHY_TX_THREAD
for
(
i
=
0
;
i
<
10
;
i
++
)
dlsch
->
active
[
i
]
=
0
;
#else
dlsch
->
active
=
0
;
#endif
dlsch
->
harq_mask
=
0
;
for
(
i
=
0
;
i
<
20
;
i
++
)
...
...
openair1/PHY/LTE_TRANSPORT/transport_eNB.h
View file @
f3b19c04
...
...
@@ -148,11 +148,7 @@ typedef struct {
/// Allocated RNTI (0 means DLSCH_t is not currently used)
uint16_t
rnti
;
/// Active flag for baseband transmitter processing
#ifdef PHY_TX_THREAD
uint8_t
active
[
10
];
#else
uint8_t
active
;
#endif
/// indicator of UE type (0 = LTE, 1,2 = Cat-M)
int
ue_type
;
/// HARQ process mask, indicates which processes are currently active
...
...
openair1/SCHED/fapi_l1.c
View file @
f3b19c04
...
...
@@ -43,6 +43,7 @@ int oai_nfapi_hi_dci0_req(nfapi_hi_dci0_request_t *hi_dci0_req);
int
oai_nfapi_ul_config_req
(
nfapi_ul_config_request_t
*
ul_config_req
);
int
oai_nfapi_ue_release_req
(
nfapi_ue_release_request_t
*
release_req
);
uint8_t
dl_pdus
[
8
][
MAX_NUM_DL_PDU
][
9422
];
void
handle_nfapi_dci_dl_pdu
(
PHY_VARS_eNB
*
eNB
,
int
frame
,
int
subframe
,
L1_rxtx_proc_t
*
proc
,
...
...
@@ -196,24 +197,13 @@ void handle_nfapi_dlsch_pdu(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_pro
AssertFatal
(
dlsch0_harq
!=
NULL
,
"dlsch_harq is null
\n
"
);
// compute DL power control parameters
eNB
->
pdsch_config_dedicated
[
UE_id
].
p_a
=
rel8
->
pa
;
#ifdef PHY_TX_THREAD
if
(
dlsch0
->
active
[
proc
->
subframe_tx
])
{
# else
if
(
dlsch0
->
active
)
{
#endif
computeRhoA_eNB
(
rel8
->
pa
,
dlsch0
,
dlsch0_harq
->
dl_power_off
,
eNB
->
frame_parms
.
nb_antenna_ports_eNB
);
computeRhoB_eNB
(
rel8
->
pa
,
eNB
->
frame_parms
.
pdsch_config_common
.
p_b
,
eNB
->
frame_parms
.
nb_antenna_ports_eNB
,
dlsch0
,
dlsch0_harq
->
dl_power_off
);
}
#ifdef PHY_TX_THREAD
if
(
dlsch1
->
active
[
proc
->
subframe_tx
])
{
#else
if
(
dlsch1
->
active
)
{
#endif
computeRhoA_eNB
(
rel8
->
pa
,
dlsch1
,
dlsch1_harq
->
dl_power_off
,
eNB
->
frame_parms
.
nb_antenna_ports_eNB
);
computeRhoB_eNB
(
rel8
->
pa
,
eNB
->
frame_parms
.
pdsch_config_common
.
p_b
,
eNB
->
frame_parms
.
nb_antenna_ports_eNB
,
dlsch1
,
dlsch1_harq
->
dl_power_off
);
}
...
...
@@ -312,11 +302,7 @@ void handle_nfapi_dlsch_pdu(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_pro
dlsch0_harq
->
rb_alloc
[
3
]
=
localRIV2alloc_LUT100_3
[
rel8
->
resource_block_coding
];
}
#ifdef PHY_TX_THREAD
dlsch0
->
active
[
proc
->
subframe_tx
]
=
1
;
#else
dlsch0
->
active
=
1
;
#endif
dlsch0_harq
->
nb_rb
=
6
;
dlsch0_harq
->
vrb_type
=
LOCALIZED
;
dlsch0_harq
->
rvidx
=
rel8
->
redundancy_version
;
...
...
@@ -359,7 +345,7 @@ void handle_nfapi_dlsch_pdu(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_pro
rel8
->
length
);
#endif
dlsch0
->
active
=
1
;
dlsch0
->
active
[
proc
->
subframe_tx
]
=
1
;
harq_pid
=
dlsch0
->
harq_ids
[
frame
%
2
][
proc
->
subframe_tx
];
dlsch0
->
harq_mask
|=
(
1
<<
harq_pid
);
AssertFatal
((
harq_pid
>=
0
)
&&
(
harq_pid
<
8
),
"subframe %d: harq_pid %d not in 0...7
\n
"
,
proc
->
subframe_tx
,
harq_pid
);
...
...
@@ -369,12 +355,12 @@ void handle_nfapi_dlsch_pdu(PHY_VARS_eNB *eNB,int frame,int subframe,L1_rxtx_pro
// compute DL power control parameters
if
(
dlsch0
->
active
)
{
if
(
dlsch0
->
active
[
proc
->
subframe_tx
]
)
{
computeRhoA_eNB
(
rel8
->
pa
,
dlsch0
,
dlsch0_harq
->
dl_power_off
,
eNB
->
frame_parms
.
nb_antenna_ports_eNB
);
computeRhoB_eNB
(
rel8
->
pa
,
eNB
->
frame_parms
.
pdsch_config_common
.
p_b
,
eNB
->
frame_parms
.
nb_antenna_ports_eNB
,
dlsch0
,
dlsch0_harq
->
dl_power_off
);
}
if
(
dlsch1
->
active
)
{
if
(
dlsch1
->
active
[
proc
->
subframe_tx
]
)
{
computeRhoA_eNB
(
rel8
->
pa
,
dlsch1
,
dlsch1_harq
->
dl_power_off
,
eNB
->
frame_parms
.
nb_antenna_ports_eNB
);
computeRhoB_eNB
(
rel8
->
pa
,
eNB
->
frame_parms
.
pdsch_config_common
.
p_b
,
eNB
->
frame_parms
.
nb_antenna_ports_eNB
,
dlsch1
,
dlsch1_harq
->
dl_power_off
);
}
...
...
@@ -867,8 +853,33 @@ void schedule_response(Sched_Rsp_t *Sched_INFO) {
dlsch_pdu_rel8
->
transport_blocks
);
if
(
1
)
{
//sdu != NULL)
if
(
NFAPI_MODE
!=
NFAPI_MODE_VNF
)
handle_nfapi_dlsch_pdu
(
eNB
,
NFAPI_SFNSF2SFN
(
DL_req
->
sfn_sf
),
NFAPI_SFNSF2SF
(
DL_req
->
sfn_sf
),
proc
,
dl_config_pdu
,
dlsch_pdu_rel8
->
transport_blocks
-
1
,
sdu
);
if
(
NFAPI_MODE
!=
NFAPI_MODE_VNF
)
{
if
(
sdu
!=
NULL
)
{
LTE_eNB_PDCCH
*
pdcch_vars
=
&
eNB
->
pdcch_vars
[
NFAPI_SFNSF2SF
(
DL_req
->
sfn_sf
)
&
1
];
uint8_t
harq_pid_dl
=
0
;
uint8_t
k
;
for
(
k
=
0
;
k
<
pdcch_vars
->
num_dci
;
k
++
){
if
(
pdcch_vars
->
dci_alloc
[
k
].
rnti
==
dlsch_pdu_rel8
->
rnti
){
harq_pid_dl
=
pdcch_vars
->
dci_alloc
[
pdcch_vars
->
num_dci
-
1
].
harq_pid
;
break
;
}
}
if
(
k
==
pdcch_vars
->
num_dci
){
LOG_E
(
PHY
,
"schedule_response not find dl harq_pid rnti %x frame %d subframe %d
\n
"
,
dlsch_pdu_rel8
->
rnti
,
NFAPI_SFNSF2SFN
(
DL_req
->
sfn_sf
),
NFAPI_SFNSF2SF
(
DL_req
->
sfn_sf
));
}
else
{
if
(
harq_pid_dl
>=
0
&&
harq_pid_dl
<
8
)
{
memset
(
dl_pdus
[
harq_pid_dl
][
i
],
0
,
sizeof
(
uint8_t
)
*
9422
);
memcpy
(
dl_pdus
[
harq_pid_dl
][
i
],
TX_req
->
tx_request_body
.
tx_pdu_list
[
pdu_index
].
segments
[
0
].
segment_data
,
TX_req
->
tx_request_body
.
tx_pdu_list
[
pdu_index
].
segments
[
0
].
segment_length
);
handle_nfapi_dlsch_pdu
(
eNB
,
NFAPI_SFNSF2SFN
(
DL_req
->
sfn_sf
),
NFAPI_SFNSF2SF
(
DL_req
->
sfn_sf
),
proc
,
dl_config_pdu
,
dlsch_pdu_rel8
->
transport_blocks
-
1
,
dl_pdus
[
harq_pid_dl
][
i
]);
}
else
{
LOG_E
(
PHY
,
"schedule_response illegal harq_pid %d
\n
"
,
harq_pid_dl
);
}
}
}
else
{
handle_nfapi_dlsch_pdu
(
eNB
,
NFAPI_SFNSF2SFN
(
DL_req
->
sfn_sf
),
NFAPI_SFNSF2SF
(
DL_req
->
sfn_sf
),
proc
,
dl_config_pdu
,
dlsch_pdu_rel8
->
transport_blocks
-
1
,
sdu
);
}
}
}
else
{
dont_send
=
1
;
LOG_E
(
MAC
,
"%s() NFAPI_DL_CONFIG_DLSCH_PDU_TYPE sdu is NULL DL_CFG:SFN/SF:%d:pdu_index:%d TX_REQ:SFN/SF:%d:pdus:%d
\n
"
,
__FUNCTION__
,
NFAPI_SFNSF2DEC
(
DL_req
->
sfn_sf
),
pdu_index
,
...
...
openair1/SCHED/phy_procedures_lte_eNb.c
View file @
f3b19c04
...
...
@@ -308,11 +308,7 @@ bool dlsch_procedures(PHY_VARS_eNB *eNB,
if
(
eNB
->
dlsch_encoding_stats
.
p_time
>
500
*
3000
&&
opp_enabled
==
1
)
{
print_meas_now
(
&
eNB
->
dlsch_encoding_stats
,
"total coding"
,
stderr
);
}
#ifdef PHY_TX_THREAD
dlsch
->
active
[
subframe
]
=
0
;
#else
dlsch
->
active
=
0
;
#endif
dlsch_harq
->
round
++
;
LOG_D
(
PHY
,
"Generated DLSCH dlsch_harq[round:%d]
\n
"
,
dlsch_harq
->
round
);
return
true
;
...
...
@@ -502,11 +498,7 @@ void phy_procedures_eNB_TX(PHY_VARS_eNB *eNB,
dlsch1
=
eNB
->
dlsch
[(
uint8_t
)
UE_id
][
1
];
if
((
dlsch0
)
&&
(
dlsch0
->
rnti
>
0
)
&&
#ifdef PHY_TX_THREAD
(
dlsch0
->
active
[
subframe
]
==
1
)
#else
(
dlsch0
->
active
==
1
)
#endif
)
{
// get harq_pid
harq_pid
=
dlsch0
->
harq_ids
[
frame
%
2
][
subframe
];
...
...
@@ -541,11 +533,7 @@ void phy_procedures_eNB_TX(PHY_VARS_eNB *eNB,
}
}
}
else
if
((
dlsch0
)
&&
(
dlsch0
->
rnti
>
0
)
&&
#ifdef PHY_TX_THREAD
(
dlsch0
->
active
[
subframe
]
==
0
)
#else
(
dlsch0
->
active
==
0
)
#endif
)
{
// clear subframe TX flag since UE is not scheduled for PDSCH in this subframe (so that we don't look for PUCCH later)
dlsch0
->
subframe_tx
[
subframe
]
=
0
;
...
...
openair2/ENB_APP/enb_config.c
View file @
f3b19c04
...
...
@@ -2131,7 +2131,7 @@ int RCconfig_gtpu(void ) {
cidr
=
enb_ipv4_address_for_S1U
;
address
=
strtok
(
cidr
,
"/"
);
if
(
address
)
{
if
(
address
&&
(
RC
.
gtpv1u_data_g
==
NULL
||
RC
.
gtpv1u_data_g
->
enb_ip_address_for_S1u_S12_S4_up
==
0
)
)
{
MessageDef
*
message
;
AssertFatal
((
message
=
itti_alloc_new_message
(
TASK_ENB_APP
,
GTPV1U_ENB_S1_REQ
))
!=
NULL
,
""
);
IPV4_STR_ADDR_TO_INT_NWBO
(
address
,
GTPV1U_ENB_S1_REQ
(
message
).
enb_ip_address_for_S1u_S12_S4_up
,
"BAD IP ADDRESS FORMAT FOR eNB S1_U !
\n
"
);
...
...
openair2/LAYER2/MAC/eNB_scheduler_fairRR.c
View file @
f3b19c04
...
...
@@ -51,10 +51,6 @@
#include "T.h"
#ifdef PHY_TX_THREAD
extern
volatile
int16_t
phy_tx_txdataF_end
;
extern
int
oai_exit
;
#endif
extern
uint16_t
sfnsf_add_subframe
(
uint16_t
frameP
,
uint16_t
subframeP
,
int
offset
);
extern
void
add_subframe
(
uint16_t
*
frameP
,
uint16_t
*
subframeP
,
int
offset
);
...
...
@@ -1655,17 +1651,6 @@ schedule_ue_spec_fairRR(module_id_t module_idP,
post_padding
=
TBS
-
sdu_length_total
-
header_len_dcch
-
header_len_dtch
-
ta_len
;
// 1 is for the postpadding header
}
#ifdef PHY_TX_THREAD
struct
timespec
time_req
,
time_rem
;
time_req
.
tv_sec
=
0
;
time_req
.
tv_nsec
=
10000
;
while
((
!
oai_exit
)
&&
(
phy_tx_txdataF_end
==
0
))
{
nanosleep
(
&
time_req
,
&
time_rem
);
continue
;
}
#endif
offset
=
generate_dlsch_header
((
unsigned
char
*
)
UE_list
->
DLSCH_pdu
[
CC_id
][
0
][
UE_id
].
payload
[
0
],
num_sdus
,
//num_sdus
sdu_lengths
,
//
sdu_lcids
,
255
,
// no drx
...
...
openair3/GTPV1-U/gtpv1u_eNB.c
View file @
f3b19c04
...
...
@@ -1216,7 +1216,7 @@ int gtpv1u_eNB_init(void) {
//gtpv1u_data_g.udp_data;
RC
.
gtpv1u_data_g
->
seq_num
=
0
;
RC
.
gtpv1u_data_g
->
restart_counter
=
0
;
RC
.
gtpv1u_data_g
->
enb_ip_address_for_S1u_S12_S4_up
=
0
;
/* Initializing GTPv1-U stack */
if
((
rc
=
nwGtpv1uInitialize
(
&
RC
.
gtpv1u_data_g
->
gtpv1u_stack
,
GTPU_STACK_ENB
))
!=
NW_GTPV1U_OK
)
{
LOG_E
(
GTPU
,
"Failed to setup nwGtpv1u stack %x
\n
"
,
rc
);
...
...
targets/RT/USER/lte-enb.c
View file @
f3b19c04
...
...
@@ -139,7 +139,7 @@ static struct {
}
sync_phy_proc
;
extern
double
cpuf
;
int
first_phy_tx
=
1
;
void
init_eNB
(
int
,
int
);
void
stop_eNB
(
int
nb_inst
);
...
...
@@ -450,6 +450,41 @@ static void *L1_thread( void *param ) {
return
&
eNB_thread_rxtx_status
;
}
static
void
*
L1_thread_rx
(
void
*
param
)
{
static
int
eNB_thread_phy_rx_status
;
L1_proc_t
*
eNB_proc
=
(
L1_proc_t
*
)
param
;
L1_rxtx_proc_t
*
proc
=
&
eNB_proc
->
L1_proc
;
PHY_VARS_eNB
*
eNB
=
RC
.
eNB
[
0
][
proc
->
CC_id
];
char
string
[
20
];
sprintf
(
string
,
"RXnp4_1"
);
// set default return value
eNB_thread_phy_rx_status
=
0
;
thread_top_init
(
string
,
1
,
500000L
,
1000000L
,
20000000L
);
while
(
!
oai_exit
)
{
if
(
wait_on_condition
(
&
proc
->
mutex
,
&
proc
->
cond
,
&
proc
->
instance_cnt
,
string
)
<
0
)
break
;
if
(
oai_exit
)
break
;
LOG_D
(
PHY
,
"Running L1 thread rx procedures
\n
"
);
if
(
rxtx
(
eNB
,
proc
,
string
)
<
0
)
LOG_E
(
PHY
,
"eNB %d CC_id %d failed during execution
\n
"
,
eNB
->
Mod_id
,
eNB
->
CC_id
);
if
(
release_thread
(
&
proc
->
mutex
,
&
proc
->
instance_cnt
,
string
)
<
0
)
break
;
}
LOG_I
(
PHY
,
"Exiting L1 thread RX
\n
"
);
eNB_thread_phy_rx_status
=
0
;
return
&
eNB_thread_phy_rx_status
;
}
void
eNB_top
(
PHY_VARS_eNB
*
eNB
,
int
frame_rx
,
int
subframe_rx
,
char
*
string
,
RU_t
*
ru
)
{
L1_proc_t
*
proc
=
&
eNB
->
proc
;
L1_rxtx_proc_t
*
L1_proc
=
&
proc
->
L1_proc
;
...
...
@@ -544,7 +579,7 @@ int wakeup_tx(PHY_VARS_eNB *eNB, int frame_rx,int subframe_rx,int frame_tx,int s
L1_rxtx_proc_t
*
L1_proc
=
&
eNB
->
proc
.
L1_proc
;
L1_rxtx_proc_t
*
L1_proc_tx
=
&
eNB
->
proc
.
L1_proc_tx
;
RU_proc_t
*
ru_proc
=
NULL
;
int
ret
;
LOG_D
(
PHY
,
"ENTERED wakeup_tx (IC %d)
\n
"
,
L1_proc_tx
->
instance_cnt
);
...
...
@@ -561,12 +596,23 @@ int wakeup_tx(PHY_VARS_eNB *eNB, int frame_rx,int subframe_rx,int frame_tx,int s
VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME
(
VCD_SIGNAL_DUMPER_VARIABLES_L1_PROC_TX_IC
,
L1_proc_tx
->
instance_cnt
);
L1_proc_tx
->
instance_cnt
=
0
;
VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME
(
VCD_SIGNAL_DUMPER_VARIABLES_L1_PROC_TX_IC
,
L1_proc_tx
->
instance_cnt
);
L1_proc_tx
->
subframe_rx
=
subframe_rx
;
L1_proc_tx
->
frame_rx
=
frame_rx
;
L1_proc_tx
->
subframe_tx
=
subframe_tx
;
L1_proc_tx
->
frame_tx
=
frame_tx
;
L1_proc_tx
->
timestamp_tx
=
timestamp_tx
;
if
(
!
((
get_thread_parallel_conf
()
==
PARALLEL_RU_L1_TRX_SPLIT
)
&&
(
get_thread_worker_conf
()
==
WORKER_ENABLE
))){
L1_proc_tx
->
subframe_rx
=
subframe_rx
;
L1_proc_tx
->
frame_rx
=
frame_rx
;
L1_proc_tx
->
subframe_tx
=
subframe_tx
;
L1_proc_tx
->
frame_tx
=
frame_tx
;
L1_proc_tx
->
timestamp_tx
=
timestamp_tx
;
}
else
{
if
(
eNB
->
RU_list
[
0
]
!=
NULL
)
ru_proc
=
&
eNB
->
RU_list
[
0
]
->
proc
;
if
(
ru_proc
!=
NULL
){
L1_proc_tx
->
subframe_rx
=
ru_proc
->
subframe_rx
;
L1_proc_tx
->
frame_rx
=
ru_proc
->
frame_rx
;
L1_proc_tx
->
subframe_tx
=
(
ru_proc
->
subframe_rx
+
(
sf_ahead
-
1
))
%
10
;
L1_proc_tx
->
frame_tx
=
(
ru_proc
->
subframe_rx
>
(
9
-
(
sf_ahead
-
1
)))
?
(
ru_proc
->
frame_rx
+
1
)
&
1023
:
ru_proc
->
frame_rx
;
L1_proc_tx
->
timestamp_tx
=
ru_proc
->
timestamp_rx
+
((
sf_ahead
-
1
)
*
eNB
->
frame_parms
.
samples_per_tti
);;
}
}
// the thread can now be woken up
LOG_D
(
PHY
,
"L1 RX Waking up L1 TX %d.%d
\n
"
,
L1_proc
->
frame_tx
,
L1_proc
->
subframe_tx
);
AssertFatal
(
pthread_cond_signal
(
&
L1_proc_tx
->
cond
)
==
0
,
"ERROR pthread_cond_signal for eNB L1 thread tx
\n
"
);
...
...
@@ -575,6 +621,36 @@ int wakeup_tx(PHY_VARS_eNB *eNB, int frame_rx,int subframe_rx,int frame_tx,int s
return
(
0
);
}
int
wakeup_rx
(
PHY_VARS_eNB
*
eNB
,
RU_t
*
ru
){
L1_proc_t
*
proc
=&
eNB
->
proc
;
RU_proc_t
*
ru_proc
=&
ru
->
proc
;
LTE_DL_FRAME_PARMS
*
fp
=
&
eNB
->
frame_parms
;
L1_rxtx_proc_t
*
L1_proc_rx
=&
proc
->
L1_proc
;
if
(
pthread_mutex_lock
(
&
L1_proc_rx
->
mutex
)
!=
0
){
LOG_E
(
PHY
,
"[RU] ERROR pthread_mutex_lock for phy rx thread (IC %d)
\n
"
,
L1_proc_rx
->
instance_cnt
);
exit_fun
(
"error locking mutex_rxtx"
);
}
if
(
L1_proc_rx
->
instance_cnt
==-
1
)
{
++
L1_proc_rx
->
instance_cnt
;
L1_proc_rx
->
timestamp_tx
=
ru_proc
->
timestamp_rx
+
(
sf_ahead
*
fp
->
samples_per_tti
);
L1_proc_rx
->
frame_rx
=
ru_proc
->
frame_rx
;
L1_proc_rx
->
subframe_rx
=
ru_proc
->
subframe_rx
;
L1_proc_rx
->
frame_tx
=
(
ru_proc
->
subframe_rx
>
(
9
-
sf_ahead
))
?
(
ru_proc
->
frame_rx
+
1
)
&
1023
:
ru_proc
->
frame_rx
;
L1_proc_rx
->
subframe_tx
=
(
ru_proc
->
subframe_rx
+
sf_ahead
)
%
10
;
// the thread can now be woken up
AssertFatal
(
pthread_cond_signal
(
&
L1_proc_rx
->
cond
)
==
0
,
"ERROR pthread_cond_signal for L1_thread_rx thread
\n
"
);
}
else
{
LOG_E
(
PHY
,
"phy rx thread busy, skipping instance_cnt_phy_rx %d
\n
"
,
L1_proc_rx
->
instance_cnt
);
}
pthread_mutex_unlock
(
&
L1_proc_rx
->
mutex
);
return
0
;
}
int
wakeup_rxtx
(
PHY_VARS_eNB
*
eNB
,
RU_t
*
ru
)
{
L1_proc_t
*
proc
=&
eNB
->
proc
;
RU_proc_t
*
ru_proc
=&
ru
->
proc
;
...
...
@@ -583,7 +659,7 @@ int wakeup_rxtx(PHY_VARS_eNB *eNB,RU_t *ru) {
int
ret
;
LOG_D
(
PHY
,
"ENTERED wakeup_rxtx, %d.%d
\n
"
,
ru_proc
->
frame_rx
,
ru_proc
->
subframe_rx
);
if
(
!
((
get_thread_parallel_conf
()
==
PARALLEL_RU_L1_TRX_SPLIT
)
&&
(
get_thread_worker_conf
()
==
WORKER_ENABLE
)))
{
// wake up TX for subframe n+sl_ahead
// lock the TX mutex and make sure the thread is ready
AssertFatal
((
ret
=
pthread_mutex_lock
(
&
L1_proc
->
mutex
))
==
0
,
"mutex_lock returns %d
\n
"
,
ret
);
...
...
@@ -623,6 +699,15 @@ int wakeup_rxtx(PHY_VARS_eNB *eNB,RU_t *ru) {
}
AssertFatal
((
ret
=
pthread_mutex_unlock
(
&
L1_proc
->
mutex
))
==
0
,
"mutex_unlock return %d
\n
"
,
ret
);
}
else
{
wakeup_rx
(
eNB
,
ru
);
// don't sent tx data when first time because this is no data
if
(
first_phy_tx
==
0
){
wakeup_tx
(
eNB
,
ru_proc
->
frame_rx
,
ru_proc
->
subframe_rx
,
(
L1_proc
->
subframe_rx
>
(
9
-
sf_ahead
))
?
(
L1_proc
->
frame_rx
+
1
)
&
1023
:
L1_proc
->
frame_rx
,
(
L1_proc
->
subframe_rx
+
sf_ahead
)
%
10
,
ru_proc
->
timestamp_rx
+
(
sf_ahead
*
fp
->
samples_per_tti
));
}
else
{
first_phy_tx
=
0
;
}
}
return
(
0
);
}
...
...
@@ -930,7 +1015,7 @@ void init_eNB_proc(int inst) {
if
((
get_thread_parallel_conf
()
==
PARALLEL_RU_L1_SPLIT
)
&&
NFAPI_MODE
!=
NFAPI_MODE_VNF
)
{
pthread_create
(
&
L1_proc
->
pthread
,
attr0
,
L1_thread
,
proc
);
}
else
if
((
get_thread_parallel_conf
()
==
PARALLEL_RU_L1_TRX_SPLIT
)
&&
NFAPI_MODE
!=
NFAPI_MODE_VNF
)
{
pthread_create
(
&
L1_proc
->
pthread
,
attr0
,
L1_thread
,
proc
);
pthread_create
(
&
L1_proc
->
pthread
,
attr0
,
L1_thread
_rx
,
proc
);
pthread_create
(
&
L1_proc_tx
->
pthread
,
attr1
,
L1_thread_tx
,
proc
);
}
else
if
(
NFAPI_MODE
==
NFAPI_MODE_VNF
)
{
// this is neccesary in VNF or L2 FAPI simulator.
// Original Code from Fujitsu w/ old structure/field name
...
...
targets/RT/USER/lte-ru.c
View file @
f3b19c04
...
...
@@ -1553,6 +1553,41 @@ void *ru_thread_tx( void *param ) {
return
0
;
}
#if defined(PRE_SCD_THREAD)
int
wakeup_prescd
(
RU_t
*
ru
,
int
frame
,
int
subframe
){
new_dlsch_ue_select_tbl_in_use
=
dlsch_ue_select_tbl_in_use
;
dlsch_ue_select_tbl_in_use
=
!
dlsch_ue_select_tbl_in_use
;
memcpy
(
&
pre_scd_eNB_UE_stats
,
&
RC
.
mac
[
ru
->
eNB_list
[
0
]
->
Mod_id
]
->
UE_list
.
eNB_UE_stats
,
sizeof
(
eNB_UE_STATS
)
*
MAX_NUM_CCs
*
NUMBER_OF_UE_MAX
);
memcpy
(
&
pre_scd_activeUE
,
&
RC
.
mac
[
ru
->
eNB_list
[
0
]
->
Mod_id
]
->
UE_list
.
active
,
sizeof
(
boolean_t
)
*
NUMBER_OF_UE_MAX
);
if
(
pthread_mutex_lock
(
&
ru
->
proc
.
mutex_pre_scd
)
!=
0
)
{
LOG_E
(
PHY
,
"[eNB] error locking proc mutex for eNB pre scd
\n
"
);
exit_fun
(
"error locking mutex_time"
);
}
ru
->
proc
.
instance_pre_scd
++
;
if
(
ru
->
proc
.
instance_pre_scd
==
0
)
{
if
(
pthread_cond_signal
(
&
ru
->
proc
.
cond_pre_scd
)
!=
0
)
{
LOG_E
(
PHY
,
"[eNB] ERROR pthread_cond_signal for eNB pre scd
\n
"
);
exit_fun
(
"ERROR pthread_cond_signal cond_pre_scd"
);
return
-
1
;
}
}
else
{
LOG_E
(
PHY
,
"[eNB] frame %d subframe %d rxtx busy instance_pre_scd %d
\n
"
,
frame
,
subframe
,
ru
->
proc
.
instance_pre_scd
);
return
-
1
;
}
if
(
pthread_mutex_unlock
(
&
ru
->
proc
.
mutex_pre_scd
)
!=
0
)
{
LOG_E
(
PHY
,
"[eNB] error unlocking mutex_pre_scd mutex for eNB pre scd
\n
"
);
exit_fun
(
"error unlocking mutex_pre_scd"
);
return
-
1
;
}
return
0
;
}
#endif
void
*
ru_thread
(
void
*
param
)
{
RU_t
*
ru
=
(
RU_t
*
)
param
;
RU_proc_t
*
proc
=
&
ru
->
proc
;
...
...
@@ -1777,26 +1812,9 @@ void *ru_thread( void *param ) {
AssertFatal
((
ret
=
pthread_mutex_unlock
(
&
proc
->
mutex_eNBs
))
==
0
,
"mutex_unlock returns %d
\n
"
,
ret
);
#if defined(PRE_SCD_THREAD)
new_dlsch_ue_select_tbl_in_use
=
dlsch_ue_select_tbl_in_use
;
dlsch_ue_select_tbl_in_use
=
!
dlsch_ue_select_tbl_in_use
;
memcpy
(
&
pre_scd_eNB_UE_stats
,
&
RC
.
mac
[
ru
->
eNB_list
[
0
]
->
Mod_id
]
->
UE_list
.
eNB_UE_stats
,
sizeof
(
eNB_UE_STATS
)
*
MAX_NUM_CCs
*
NUMBER_OF_UE_MAX
);
memcpy
(
&
pre_scd_activeUE
,
&
RC
.
mac
[
ru
->
eNB_list
[
0
]
->
Mod_id
]
->
UE_list
.
active
,
sizeof
(
boolean_t
)
*
NUMBER_OF_UE_MAX
);
AssertFatal
((
ret
=
pthread_mutex_lock
(
&
ru
->
proc
.
mutex_pre_scd
))
==
0
,
"[eNB] error locking proc mutex for eNB pre scd
\n
"
);
ru
->
proc
.
instance_pre_scd
++
;
if
(
ru
->
proc
.
instance_pre_scd
==
0
)
{
if
(
pthread_cond_signal
(
&
ru
->
proc
.
cond_pre_scd
)
!=
0
)
{
LOG_E
(
PHY
,
"[eNB] ERROR pthread_cond_signal for eNB pre scd
\n
"
);
exit_fun
(
"ERROR pthread_cond_signal cond_pre_scd"
);
}
}
else
{
LOG_E
(
PHY
,
"[eNB] frame %d subframe %d rxtx busy instance_pre_scd %d
\n
"
,
frame
,
subframe
,
ru
->
proc
.
instance_pre_scd
);
}
AssertFatal
((
ret
=
pthread_mutex_unlock
(
&
ru
->
proc
.
mutex_pre_scd
))
==
0
,
"[eNB] error unlocking mutex_pre_scd mutex for eNB pre scd
\n
"
);
if
(
NFAPI_MODE
==
NFAPI_MONOLITHIC
)
{
wakeup_prescd
(
ru
,
frame
,
subframe
);
}
#endif
// wakeup all eNB processes waiting for this RU
if
(
ru
->
num_eNB
>
0
)
wakeup_L1s
(
ru
);
...
...
@@ -2208,11 +2226,13 @@ void init_RU_proc(RU_t *ru) {
pthread_create
(
&
proc
->
pthread_FH
,
attr_FH
,
ru_thread
,
(
void
*
)
ru
);
#if defined(PRE_SCD_THREAD)
if
(
NFAPI_MODE
==
NFAPI_MONOLITHIC
)
{
proc
->
instance_pre_scd
=
-
1
;
pthread_mutex_init
(
&
proc
->
mutex_pre_scd
,
NULL
);
pthread_cond_init
(
&
proc
->
cond_pre_scd
,
NULL
);
pthread_create
(
&
proc
->
pthread_pre_scd
,
NULL
,
pre_scd_thread
,
(
void
*
)
ru
);
pthread_setname_np
(
proc
->
pthread_pre_scd
,
"pre_scd_thread"
);
}
#endif
#ifdef PHY_TX_THREAD
pthread_create
(
&
proc
->
pthread_phy_tx
,
NULL
,
eNB_thread_phy_tx
,
(
void
*
)
ru
);
...
...
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