Commit 2e089075 authored by Laurent's avatar Laurent

Move split 6 to do rate matching (puncturing) in CU

parent 708076f6
...@@ -276,9 +276,9 @@ rfsimulator : ...@@ -276,9 +276,9 @@ rfsimulator :
global_log_verbosity ="medium"; global_log_verbosity ="medium";
hw_log_level ="info"; hw_log_level ="info";
hw_log_verbosity ="medium"; hw_log_verbosity ="medium";
phy_log_level ="info"; phy_log_level ="debug";
phy_log_verbosity ="medium"; phy_log_verbosity ="medium";
mac_log_level ="info"; mac_log_level ="debug";
mac_log_verbosity ="high"; mac_log_verbosity ="high";
rlc_log_level ="info"; rlc_log_level ="info";
rlc_log_verbosity ="medium"; rlc_log_verbosity ="medium";
......
...@@ -272,7 +272,8 @@ void sendFs6Ulharq(enum pckType type, int UEid, PHY_VARS_eNB *eNB, LTE_eNB_UCI * ...@@ -272,7 +272,8 @@ void sendFs6Ulharq(enum pckType type, int UEid, PHY_VARS_eNB *eNB, LTE_eNB_UCI *
newUDPheader->contentBytes+=sizeof(fs6_ul_uespec_uci_element_t); newUDPheader->contentBytes+=sizeof(fs6_ul_uespec_uci_element_t);
} }
void sendFs6Ul(PHY_VARS_eNB *eNB, int UE_id, int harq_pid, int segmentID, int16_t *data, int dataLen) {
void sendFs6Ul(PHY_VARS_eNB *eNB, int UE_id, int harq_pid, int segmentID, int16_t *data, int dataLen, int r_offset) {
uint8_t *bufferZone=eNB->FS6bufferZone; uint8_t *bufferZone=eNB->FS6bufferZone;
commonUDP_t *FirstUDPheader=(commonUDP_t *) bufferZone; commonUDP_t *FirstUDPheader=(commonUDP_t *) bufferZone;
// move to the end // move to the end
...@@ -298,13 +299,15 @@ void sendFs6Ul(PHY_VARS_eNB *eNB, int UE_id, int harq_pid, int segmentID, int16_ ...@@ -298,13 +299,15 @@ void sendFs6Ul(PHY_VARS_eNB *eNB, int UE_id, int harq_pid, int segmentID, int16_
hULUE(newUDPheader)->cqi_crc_status=eNB->ulsch[UE_id]->harq_processes[harq_pid]->cqi_crc_status; hULUE(newUDPheader)->cqi_crc_status=eNB->ulsch[UE_id]->harq_processes[harq_pid]->cqi_crc_status;
hULUE(newUDPheader)->O_ACK=eNB->ulsch[UE_id]->harq_processes[harq_pid]->O_ACK; hULUE(newUDPheader)->O_ACK=eNB->ulsch[UE_id]->harq_processes[harq_pid]->O_ACK;
memcpy(hULUE(newUDPheader)->o_ACK, eNB->ulsch[UE_id]->harq_processes[harq_pid]->o_ACK, memcpy(hULUE(newUDPheader)->o_ACK, eNB->ulsch[UE_id]->harq_processes[harq_pid]->o_ACK,
sizeof(eNB->ulsch[UE_id]->harq_processes[harq_pid]->o_ACK)); sizeof(eNB->ulsch[UE_id]->harq_processes[harq_pid]->o_ACK));
hULUE(newUDPheader)->ta=lte_est_timing_advance_pusch(eNB, UE_id); hULUE(newUDPheader)->ta=lte_est_timing_advance_pusch(eNB, UE_id);
hULUE(newUDPheader)->segment=segmentID; hULUE(newUDPheader)->segment=segmentID;
memcpy(hULUE(newUDPheader)->o, eNB->ulsch[UE_id]->harq_processes[harq_pid]->o, memcpy(hULUE(newUDPheader)->o, eNB->ulsch[UE_id]->harq_processes[harq_pid]->o,
sizeof(eNB->ulsch[UE_id]->harq_processes[harq_pid]->o)); sizeof(eNB->ulsch[UE_id]->harq_processes[harq_pid]->o));
memcpy(hULUE(newUDPheader)+1, data, dataLen); memcpy(hULUE(newUDPheader)+1, data, dataLen);
hULUE(newUDPheader)->segLen=dataLen; hULUE(newUDPheader)->segLen=dataLen;
hULUE(newUDPheader)->r_offset=r_offset;
hULUE(newUDPheader)->G=eNB->ulsch[UE_id]->harq_processes[harq_pid]->G;
} }
void pusch_procedures_tosplit(uint8_t *bufferZone, int bufSize, PHY_VARS_eNB *eNB,L1_rxtx_proc_t *proc) { void pusch_procedures_tosplit(uint8_t *bufferZone, int bufSize, PHY_VARS_eNB *eNB,L1_rxtx_proc_t *proc) {
...@@ -427,102 +430,6 @@ void phy_procedures_eNB_uespec_RX_tosplit(uint8_t *bufferZone, int bufSize, PHY_ ...@@ -427,102 +430,6 @@ void phy_procedures_eNB_uespec_RX_tosplit(uint8_t *bufferZone, int bufSize, PHY_
return; return;
} }
int ulsch_decoding_process(PHY_VARS_eNB *eNB, int UE_id, int llr8_flag) {
int harq_pid;
LTE_eNB_ULSCH_t *ulsch = eNB->ulsch[UE_id];
if (ulsch->ue_type>0)
harq_pid = 0;
else
harq_pid = subframe2harq_pid(&eNB->frame_parms,eNB->proc.frame_rx,eNB->proc.subframe_rx);
LTE_UL_eNB_HARQ_t *ulsch_harq = ulsch->harq_processes[harq_pid];
decoder_if_t *tc;
int offset, ret=-1;
if (llr8_flag == 0)
tc = *decoder16;
else
tc = *decoder8;
// This is a new packet, so compute quantities regarding segmentation
// Fixme: very dirty: all the variables produced by let_segmentation are only used localy
// Furthermore, variables as 1 letter and global is "time consuming" for everybody !!!
ulsch_harq->B = ulsch_harq->TBS+24;
lte_segmentation(NULL,
NULL,
ulsch_harq->B,
&ulsch_harq->C,
&ulsch_harq->Cplus,
&ulsch_harq->Cminus,
&ulsch_harq->Kplus,
&ulsch_harq->Kminus,
&ulsch_harq->F);
for (int r=0; r<ulsch_harq->C; r++) {
// printf("before subblock deinterleaving c[%d] = %p\n",r,ulsch_harq->c[r]);
// Get Turbo interleaver parameters
int Kr;
if (r<ulsch_harq->Cminus)
Kr = ulsch_harq->Kminus;
else
Kr = ulsch_harq->Kplus;
int Kr_bytes = Kr>>3;
int crc_type;
if (ulsch_harq->C == 1)
crc_type = CRC24_A;
else
crc_type = CRC24_B;
start_meas(&eNB->ulsch_turbo_decoding_stats);
ret = tc(&ulsch_harq->d[r][96],
NULL,
ulsch_harq->c[r],
NULL,
Kr,
ulsch->max_turbo_iterations,//MAX_TURBO_ITERATIONS,
crc_type,
(r==0) ? ulsch_harq->F : 0,
&eNB->ulsch_tc_init_stats,
&eNB->ulsch_tc_alpha_stats,
&eNB->ulsch_tc_beta_stats,
&eNB->ulsch_tc_gamma_stats,
&eNB->ulsch_tc_ext_stats,
&eNB->ulsch_tc_intl1_stats,
&eNB->ulsch_tc_intl2_stats);
stop_meas(&eNB->ulsch_turbo_decoding_stats);
// Reassembly of Transport block here
if (ret != (1+ulsch->max_turbo_iterations)) {
if (r<ulsch_harq->Cminus)
Kr = ulsch_harq->Kminus;
else
Kr = ulsch_harq->Kplus;
Kr_bytes = Kr>>3;
if (r==0) {
memcpy(ulsch_harq->b,
&ulsch_harq->c[0][(ulsch_harq->F>>3)],
Kr_bytes - (ulsch_harq->F>>3) - ((ulsch_harq->C>1)?3:0));
offset = Kr_bytes - (ulsch_harq->F>>3) - ((ulsch_harq->C>1)?3:0);
} else {
memcpy(ulsch_harq->b+offset,
ulsch_harq->c[r],
Kr_bytes - ((ulsch_harq->C>1)?3:0));
offset += (Kr_bytes- ((ulsch_harq->C>1)?3:0));
}
} else {
break;
}
}
return(ret);
}
void fill_rx_indication_from_split(uint8_t *bufferZone, PHY_VARS_eNB *eNB,int UE_id,int frame,int subframe, ul_propagation_t *ul_propa) { void fill_rx_indication_from_split(uint8_t *bufferZone, PHY_VARS_eNB *eNB,int UE_id,int frame,int subframe, ul_propagation_t *ul_propa) {
nfapi_rx_indication_pdu_t *pdu; nfapi_rx_indication_pdu_t *pdu;
...@@ -645,16 +552,19 @@ void pusch_procedures_fromsplit(uint8_t *bufferZone, int bufSize, PHY_VARS_eNB * ...@@ -645,16 +552,19 @@ void pusch_procedures_fromsplit(uint8_t *bufferZone, int bufSize, PHY_VARS_eNB *
} }
start_meas(&eNB->ulsch_decoding_stats); start_meas(&eNB->ulsch_decoding_stats);
/* // This is a new packet, so compute quantities regarding segmentation
int ret = ulsch_decoding(eNB,&eNB->proc.L1_proc, ulsch_harq->B = ulsch_harq->TBS+24;
i, lte_segmentation(NULL,
0, // control_only_flag NULL,
ulsch_harq->V_UL_DAI, ulsch_harq->B,
ulsch_harq->nb_rb>20 ? 1 : 0); &ulsch_harq->C,
*/ &ulsch_harq->Cplus,
int ret = ulsch_decoding_process(eNB, &ulsch_harq->Cminus,
i, &ulsch_harq->Kplus,
ulsch_harq->nb_rb>20 ? 1 : 0); &ulsch_harq->Kminus,
&ulsch_harq->F);
int ret = ulsch_decoding_data(eNB, i, harq_pid,
ulsch_harq->nb_rb>20 ? 1 : 0);
stop_meas(&eNB->ulsch_decoding_stats); stop_meas(&eNB->ulsch_decoding_stats);
LOG_D(PHY, LOG_D(PHY,
"[eNB %d][PUSCH %d] frame %d subframe %d RNTI %x RX power (%d,%d) N0 (%d,%d) dB ACK (%d,%d), decoding iter %d ulsch_harq->cqi_crc_status:%d ackBits:%d ulsch_decoding_stats[t:%lld max:%lld]\n", "[eNB %d][PUSCH %d] frame %d subframe %d RNTI %x RX power (%d,%d) N0 (%d,%d) dB ACK (%d,%d), decoding iter %d ulsch_harq->cqi_crc_status:%d ackBits:%d ulsch_decoding_stats[t:%lld max:%lld]\n",
...@@ -781,20 +691,21 @@ void recvFs6Ul(uint8_t *bufferZone, int nbBlocks, PHY_VARS_eNB *eNB, ul_propagat ...@@ -781,20 +691,21 @@ void recvFs6Ul(uint8_t *bufferZone, int nbBlocks, PHY_VARS_eNB *eNB, ul_propagat
if ( type == fs6ULsch) { if ( type == fs6ULsch) {
LTE_eNB_ULSCH_t *ulsch =eNB->ulsch[hULUE(bufPtr)->UE_id]; LTE_eNB_ULSCH_t *ulsch =eNB->ulsch[hULUE(bufPtr)->UE_id];
LTE_UL_eNB_HARQ_t *ulsch_harq=ulsch->harq_processes[hULUE(bufPtr)->harq_id]; LTE_UL_eNB_HARQ_t *ulsch_harq=ulsch->harq_processes[hULUE(bufPtr)->harq_id];
memcpy(&ulsch_harq->d[hULUE(bufPtr)->segment][96], memcpy(ulsch_harq->e+hULUE(bufPtr)->r_offset,
hULUE(bufPtr)+1, hULUE(bufPtr)+1,
hULUE(bufPtr)->segLen); hULUE(bufPtr)->segLen);
memcpy(eNB->pusch_vars[hULUE(bufPtr)->UE_id]->ulsch_power, memcpy(eNB->pusch_vars[hULUE(bufPtr)->UE_id]->ulsch_power,
hULUE(bufPtr)->ulsch_power, hULUE(bufPtr)->ulsch_power,
sizeof(int)*2); sizeof(int)*2);
ulsch_harq->G=hULUE(bufPtr)->G;
ulsch_harq->cqi_crc_status=hULUE(bufPtr)->cqi_crc_status; ulsch_harq->cqi_crc_status=hULUE(bufPtr)->cqi_crc_status;
//ulsch_harq->O_ACK= hULUE(bufPtr)->O_ACK; //ulsch_harq->O_ACK= hULUE(bufPtr)->O_ACK;
memcpy(ulsch_harq->o_ACK, hULUE(bufPtr)->o_ACK, memcpy(ulsch_harq->o_ACK, hULUE(bufPtr)->o_ACK,
sizeof(ulsch_harq->o_ACK)); sizeof(ulsch_harq->o_ACK));
memcpy(ulsch_harq->o,hULUE(bufPtr)->o, sizeof(ulsch_harq->o)); memcpy(ulsch_harq->o,hULUE(bufPtr)->o, sizeof(ulsch_harq->o));
ul_propa[hULUE(bufPtr)->UE_id].ta=hULUE(bufPtr)->ta; ul_propa[hULUE(bufPtr)->UE_id].ta=hULUE(bufPtr)->ta;
LOG_D(PHY,"Received ulsch data for: rnti:%x, fsf: %d/%d, cqi_crc_status %d O_ACK: %di, segment: %di, seglen: %d \n", LOG_D(PHY,"Received ulsch data for: rnti:%x, fsf: %d/%d, cqi_crc_status %d O_ACK: %di, segment: %di, seglen: %d \n",
ulsch->rnti, eNB->proc.frame_rx, eNB->proc.subframe_rx, ulsch_harq->cqi_crc_status, ulsch_harq->O_ACK,hULUE(bufPtr)->segment , hULUE(bufPtr)->segLen); ulsch->rnti, eNB->proc.frame_rx, eNB->proc.subframe_rx, ulsch_harq->cqi_crc_status, ulsch_harq->O_ACK,hULUE(bufPtr)->segment, hULUE(bufPtr)->segLen);
} else if ( type == fs6ULcch ) { } else if ( type == fs6ULcch ) {
int nb_uci=hULUEuci(bufPtr)->nb_active_ue; int nb_uci=hULUEuci(bufPtr)->nb_active_ue;
fs6_ul_uespec_uci_element_t *tmp=(fs6_ul_uespec_uci_element_t *)(hULUEuci(bufPtr)+1); fs6_ul_uespec_uci_element_t *tmp=(fs6_ul_uespec_uci_element_t *)(hULUEuci(bufPtr)+1);
...@@ -1157,6 +1068,7 @@ void appendFs6TxULUE(uint8_t *bufferZone, LTE_DL_FRAME_PARMS *fp, int curUE, LTE ...@@ -1157,6 +1068,7 @@ void appendFs6TxULUE(uint8_t *bufferZone, LTE_DL_FRAME_PARMS *fp, int curUE, LTE
memcpyToDuHarq(total_number_of_repetitions); memcpyToDuHarq(total_number_of_repetitions);
LOG_D(PHY,"Added request to perform ulsch for: rnti:%x, fsf: %d/%d\n", ulsch->rnti, frame, subframe); LOG_D(PHY,"Added request to perform ulsch for: rnti:%x, fsf: %d/%d\n", ulsch->rnti, frame, subframe);
} }
void appendFs6DLUE(uint8_t *bufferZone, LTE_DL_FRAME_PARMS *fp, int UE_id, int8_t harq_pid, LTE_eNB_DLSCH_t *dlsch0, LTE_DL_eNB_HARQ_t *harqData, int frame, int subframe) { void appendFs6DLUE(uint8_t *bufferZone, LTE_DL_FRAME_PARMS *fp, int UE_id, int8_t harq_pid, LTE_eNB_DLSCH_t *dlsch0, LTE_DL_eNB_HARQ_t *harqData, int frame, int subframe) {
commonUDP_t *FirstUDPheader=(commonUDP_t *) bufferZone; commonUDP_t *FirstUDPheader=(commonUDP_t *) bufferZone;
// move to the end // move to the end
......
...@@ -173,6 +173,8 @@ typedef struct { ...@@ -173,6 +173,8 @@ typedef struct {
uint8_t harq_id; uint8_t harq_id;
uint8_t segment; uint8_t segment;
int segLen; int segLen;
int r_offset;
int G;
int ulsch_power[2]; int ulsch_power[2];
uint8_t o_ACK[4]; uint8_t o_ACK[4];
uint8_t O_ACK; uint8_t O_ACK;
...@@ -230,7 +232,7 @@ static inline void *commonUDPdata(uint8_t *ptr) { ...@@ -230,7 +232,7 @@ static inline void *commonUDPdata(uint8_t *ptr) {
void setAllfromTS(uint64_t TS); void setAllfromTS(uint64_t TS);
void sendFs6Ulharq(enum pckType type, int UEid, PHY_VARS_eNB *eNB,LTE_eNB_UCI *uci, int frame, int subframe, uint8_t *harq_ack, uint8_t tdd_mapping_mode, uint16_t tdd_multiplexing_mask, void sendFs6Ulharq(enum pckType type, int UEid, PHY_VARS_eNB *eNB,LTE_eNB_UCI *uci, int frame, int subframe, uint8_t *harq_ack, uint8_t tdd_mapping_mode, uint16_t tdd_multiplexing_mask,
uint16_t rnti, int32_t stat); uint16_t rnti, int32_t stat);
void sendFs6Ul(PHY_VARS_eNB *eNB, int UE_id, int harq_pid, int segmentID, int16_t *data, int dataLen); void sendFs6Ul(PHY_VARS_eNB *eNB, int UE_id, int harq_pid, int segmentID, int16_t *data, int dataLen, int r_offset);
void *cu_fs6(void *arg); void *cu_fs6(void *arg);
void *du_fs6(void *arg); void *du_fs6(void *arg);
void fill_rf_config(RU_t *ru, char *rf_config_file); void fill_rf_config(RU_t *ru, char *rf_config_file);
......
...@@ -279,14 +279,6 @@ int ulsch_decoding_data_2thread0(td_params *tdp) { ...@@ -279,14 +279,6 @@ int ulsch_decoding_data_2thread0(td_params *tdp) {
ulsch_harq->RTC[r] = generate_dummy_w(4+(Kr_bytes*8), ulsch_harq->RTC[r] = generate_dummy_w(4+(Kr_bytes*8),
(uint8_t *)&dummy_w[r][0], (uint8_t *)&dummy_w[r][0],
(r==0) ? ulsch_harq->F : 0); (r==0) ? ulsch_harq->F : 0);
#ifdef DEBUG_ULSCH_DECODING
printf("Rate Matching Segment %u (coded bits (G) %d,unpunctured/repeated bits %u, Q_m %d, nb_rb %d, Nl %d)...\n",
r, G,
Kr*3,
Q_m,
nb_rb,
ulsch_harq->Nl);
#endif
if (lte_rate_matching_turbo_rx(ulsch_harq->RTC[r], if (lte_rate_matching_turbo_rx(ulsch_harq->RTC[r],
G, G,
...@@ -453,14 +445,6 @@ int ulsch_decoding_data_2thread(PHY_VARS_eNB *eNB,int UE_id,int harq_pid,int llr ...@@ -453,14 +445,6 @@ int ulsch_decoding_data_2thread(PHY_VARS_eNB *eNB,int UE_id,int harq_pid,int llr
ulsch_harq->RTC[r] = generate_dummy_w(4+(Kr_bytes*8), ulsch_harq->RTC[r] = generate_dummy_w(4+(Kr_bytes*8),
(uint8_t *)&dummy_w[r][0], (uint8_t *)&dummy_w[r][0],
(r==0) ? ulsch_harq->F : 0); (r==0) ? ulsch_harq->F : 0);
#ifdef DEBUG_ULSCH_DECODING
printf("Rate Matching Segment %u (coded bits (G) %d,unpunctured/repeated bits %u, Q_m %d, nb_rb %d, Nl %d)...\n",
r, G,
Kr*3,
Q_m,
nb_rb,
ulsch_harq->Nl);
#endif
start_meas(&eNB->ulsch_rate_unmatching_stats); start_meas(&eNB->ulsch_rate_unmatching_stats);
if (lte_rate_matching_turbo_rx(ulsch_harq->RTC[r], if (lte_rate_matching_turbo_rx(ulsch_harq->RTC[r],
...@@ -583,13 +567,29 @@ int ulsch_decoding_data(PHY_VARS_eNB *eNB,int UE_id,int harq_pid,int llr8_flag) ...@@ -583,13 +567,29 @@ int ulsch_decoding_data(PHY_VARS_eNB *eNB,int UE_id,int harq_pid,int llr8_flag)
(uint8_t *)&dummy_w[r][0], (uint8_t *)&dummy_w[r][0],
(r==0) ? ulsch_harq->F : 0); (r==0) ? ulsch_harq->F : 0);
#ifdef DEBUG_ULSCH_DECODING #ifdef DEBUG_ULSCH_DECODING
printf("Rate Matching Segment %u (coded bits (G) %d,unpunctured/repeated bits %u, Q_m %d, nb_rb %d, Nl %d)...\n", printf("Rate Matching Segment %u (coded bits (G) %d,unpunctured/repeated bits %u, Q_m %d, Nl %d, r_offset %d)...\n",
r, G, r, G,
Kr*3, Kr*3,
ulsch_harq->Qm, ulsch_harq->Qm,
ulsch_harq->Nl); ulsch_harq->Nl, r_offset);
#endif #endif
start_meas(&eNB->ulsch_rate_unmatching_stats); start_meas(&eNB->ulsch_rate_unmatching_stats);
#ifdef FS6
if ( getenv("fs6") != NULL && strncasecmp( getenv("fs6"), "du", 2) == 0 ) {
int Gp=G/ulsch_harq->Qm;
int GpmodC = Gp%ulsch_harq->C;
if (r < (ulsch_harq->C-(GpmodC)))
E = ulsch_harq->Qm * (Gp/ulsch_harq->C);
else
E = ulsch_harq->Qm * ((GpmodC==0?0:1) + (Gp/ulsch_harq->C));
sendFs6Ul(eNB, UE_id, harq_pid, r, ulsch_harq->e+r_offset, E*sizeof(int16_t), r_offset);
r_offset += E;
continue;
}
#endif
if (lte_rate_matching_turbo_rx(ulsch_harq->RTC[r], if (lte_rate_matching_turbo_rx(ulsch_harq->RTC[r],
G, G,
...@@ -639,39 +639,6 @@ int ulsch_decoding_data(PHY_VARS_eNB *eNB,int UE_id,int harq_pid,int llr8_flag) ...@@ -639,39 +639,6 @@ int ulsch_decoding_data(PHY_VARS_eNB *eNB,int UE_id,int harq_pid,int llr8_flag)
crc_type = CRC24_A; crc_type = CRC24_A;
else else
crc_type = CRC24_B; crc_type = CRC24_B;
#ifdef FS6
if ( getenv("fs6") != NULL && strncasecmp( getenv("fs6"), "du", 2) == 0 ) {
// r is the segment id,
// Kr is the segment length in short
// *3 because LTE redudancy scheme
sendFs6Ul(eNB, UE_id, harq_pid, r, &ulsch_harq->d[r][96], Kr*sizeof(int16_t)*3);
/*
int iter=tc(&ulsch_harq->d[r][96],
NULL,
ulsch_harq->c[r],
NULL,
Kr,
ulsch->max_turbo_iterations,//MAX_TURBO_ITERATIONS,
crc_type,
(r==0) ? ulsch_harq->F : 0,
&eNB->ulsch_tc_init_stats,
&eNB->ulsch_tc_alpha_stats,
&eNB->ulsch_tc_beta_stats,
&eNB->ulsch_tc_gamma_stats,
&eNB->ulsch_tc_ext_stats,
&eNB->ulsch_tc_intl1_stats,
&eNB->ulsch_tc_intl2_stats);
LOG_D(PHY, "Cu should decode in %d iter\n",iter);
if ( 1) { //iter == 5 ) {
for (int i=0; i < Kr; i++ )
printf("%hhx:", ulsch_harq->d[r][96+i]);
printf("\n");
}
*/
continue;
}
#endif
start_meas(&eNB->ulsch_turbo_decoding_stats); start_meas(&eNB->ulsch_turbo_decoding_stats);
ret = tc(&ulsch_harq->d[r][96], ret = tc(&ulsch_harq->d[r][96],
NULL, NULL,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment