/* * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The OpenAirInterface Software Alliance licenses this file to You under * the OAI Public License, Version 1.1 (the "License"); you may not use this file * except in compliance with the License. * You may obtain a copy of the License at * * http://www.openairinterface.org/?page_id=698 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *------------------------------------------------------------------------------- * For more information about the OpenAirInterface (OAI) Software Alliance: * contact@openairinterface.org */ #include #include #include #include #include #include #include #include #include #define TELNETSERVERCODE #include "telnetsrv.h" #include "openair2/RRC/NR/nr_rrc_defs.h" #include "openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h" #include "openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h" #include "openair2/LAYER2/NR_MAC_gNB/mac_proto.h" #include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c" #include "common/utils/nr/nr_common.h" #define ERROR_MSG_RET(mSG, aRGS...) do { prnt("FAILURE: " mSG, ##aRGS); return 1; } while (0) #define ISINITBWP "bwp3gpp:isInitialBwp" //#define CYCLPREF "bwp3gpp:cyclicPrefix" #define NUMRBS "bwp3gpp:numberOfRBs" #define STARTRB "bwp3gpp:startRB" #define BWPSCS "bwp3gpp:subCarrierSpacing" #define SSBFREQ "nrcelldu3gpp:ssbFrequency" #define ARFCNDL "nrcelldu3gpp:arfcnDL" #define BWDL "nrcelldu3gpp:bSChannelBwDL" #define ARFCNUL "nrcelldu3gpp:arfcnUL" #define BWUL "nrcelldu3gpp:bSChannelBwUL" #define PCI "nrcelldu3gpp:nRPCI" #define TAC "nrcelldu3gpp:nRTAC" #define MCC "nrcelldu3gpp:mcc" #define MNC "nrcelldu3gpp:mnc" #define SD "nrcelldu3gpp:sd" #define SST "nrcelldu3gpp:sst" typedef struct b { long int dl; long int ul; } b_t; typedef struct ue_stat { rnti_t rnti; b_t thr; } ue_stat_t; #define PRINTLIST_i(len, fmt, ...) \ { \ for (int i = 0; i < len; ++i) { \ if (i != 0) prnt(", "); \ prnt(fmt, __VA_ARGS__); \ } \ } \ static int get_stats(char *buf, int debug, telnet_printfunc_t prnt) { if (buf) ERROR_MSG_RET("no parameter allowed\n"); gNB_MAC_INST *mac = RC.nrmac[0]; AssertFatal(mac != NULL, "need MAC\n"); NR_SCHED_LOCK(&mac->sched_lock); const f1ap_setup_req_t *sr = mac->f1_config.setup_req; const f1ap_served_cell_info_t *cell_info = &sr->cell[0].info; const NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon; const NR_FrequencyInfoDL_t *frequencyInfoDL = scc->downlinkConfigCommon->frequencyInfoDL; const NR_FrequencyInfoUL_t *frequencyInfoUL = scc->uplinkConfigCommon->frequencyInfoUL; frame_type_t frame_type = get_frame_type(*frequencyInfoDL->frequencyBandList.list.array[0], *scc->ssbSubcarrierSpacing); const NR_BWP_t *initialDL = &scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters; const NR_BWP_t *initialUL = &scc->uplinkConfigCommon->initialUplinkBWP->genericParameters; int scs = initialDL->subcarrierSpacing; AssertFatal(scs == initialUL->subcarrierSpacing, "different SCS for UL/DL not supported!\n"); int band = *frequencyInfoDL->frequencyBandList.list.array[0]; int nrb = frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth; AssertFatal(nrb == frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth, "different BW for UL/DL not supported!\n"); frequency_range_t fr = band > 256 ? FR2 : FR1; int bw_index = get_supported_band_index(scs, fr, nrb); int bw_mhz = get_supported_bw_mhz(fr, bw_index); const dlul_mac_stats_t *stat = &mac->mac_stats; static dlul_mac_stats_t last = {0}; int diff_used = stat->dl.used_prb_aggregate - last.dl.used_prb_aggregate; int diff_total = stat->dl.total_prb_aggregate - last.dl.total_prb_aggregate; int load = diff_total > 0 ? 100 * diff_used / diff_total : 0; last = *stat; static struct timespec tp_last = {0}; struct timespec tp_now; clock_gettime(CLOCK_MONOTONIC, &tp_now); size_t diff_msec = (tp_now.tv_sec - tp_last.tv_sec) * 1000 + (tp_now.tv_nsec - tp_last.tv_nsec) / 1000000; tp_last = tp_now; const int srb_flag = 0; const int rb_id = 1; static b_t last_total[MAX_MOBILES_PER_GNB] = {0}; // TODO: hash table? ue_stat_t ue_stat[MAX_MOBILES_PER_GNB] = {0}; int num_ues = 0; UE_iterator((NR_UE_info_t **)mac->UE_info.connected_ue_list, it) { nr_rlc_statistics_t rlc = {0}; nr_rlc_get_statistics(it->rnti, srb_flag, rb_id, &rlc); b_t *lt = &last_total[num_ues]; ue_stat_t *ue_s = &ue_stat[num_ues]; ue_s->rnti = it->rnti; // static var last_total: we might have old data, larger than what // reports RLC, leading to a huge number -> cut off to zero if (lt->dl > rlc.txpdu_bytes) lt->dl = rlc.txpdu_bytes; if (lt->ul > rlc.rxpdu_bytes) lt->ul = rlc.rxpdu_bytes; ue_s->thr.dl = (rlc.txpdu_bytes - lt->dl) * 8 / diff_msec; ue_s->thr.ul = (rlc.rxpdu_bytes - lt->ul) * 8 / diff_msec; lt->dl = rlc.txpdu_bytes; lt->ul = rlc.rxpdu_bytes; num_ues++; } prnt("{\n"); prnt(" \"o1-config\": {\n"); prnt(" \"BWP\": {\n"); prnt(" \"dl\": [{\n"); prnt(" \"" ISINITBWP "\": true,\n"); //prnt(" \"" CYCLPREF "\": %ld,\n", *initialDL->cyclicPrefix); prnt(" \"" NUMRBS "\": %ld,\n", NRRIV2BW(initialDL->locationAndBandwidth, MAX_BWP_SIZE)); prnt(" \"" STARTRB "\": %ld,\n", NRRIV2PRBOFFSET(initialDL->locationAndBandwidth, MAX_BWP_SIZE)); prnt(" \"" BWPSCS "\": %ld\n", 15 * (1U << scs)); prnt(" }],\n"); prnt(" \"ul\": [{\n"); prnt(" \"" ISINITBWP "\": true,\n"); //prnt(" \"" CYCLPREF "\": %ld,\n", *initialUL->cyclicPrefix); prnt(" \"" NUMRBS "\": %ld,\n", NRRIV2BW(initialUL->locationAndBandwidth, MAX_BWP_SIZE)); prnt(" \"" STARTRB "\": %ld,\n", NRRIV2PRBOFFSET(initialUL->locationAndBandwidth, MAX_BWP_SIZE)); prnt(" \"" BWPSCS "\": %ld\n", 15 * (1U << scs)); prnt(" }]\n"); prnt(" },\n"); prnt(" \"NRCELLDU\": {\n"); prnt(" \"" SSBFREQ "\": %ld,\n", *scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencySSB); prnt(" \"" ARFCNDL "\": %ld,\n", frequencyInfoDL->absoluteFrequencyPointA); prnt(" \"" BWDL "\": %ld,\n", bw_mhz); prnt(" \"" ARFCNUL "\": %ld,\n", frequencyInfoUL->absoluteFrequencyPointA ? *frequencyInfoUL->absoluteFrequencyPointA : frequencyInfoDL->absoluteFrequencyPointA); prnt(" \"" BWUL "\": %ld,\n", bw_mhz); prnt(" \"" PCI "\": %ld,\n", *scc->physCellId); prnt(" \"" TAC "\": %ld,\n", *cell_info->tac); prnt(" \"" MCC "\": \"%03d\",\n", cell_info->plmn.mcc); prnt(" \"" MNC "\": \"%0*d\",\n", cell_info->plmn.mnc_digit_length, cell_info->plmn.mnc); prnt(" \"" SD "\": %d,\n", cell_info->nssai[0].sd); prnt(" \"" SST "\": %d\n", cell_info->nssai[0].sst); prnt(" },\n"); prnt(" \"device\": {\n"); prnt(" \"gnbId\": %d,\n", sr->gNB_DU_id); prnt(" \"gnbName\": \"%s\",\n", sr->gNB_DU_name); prnt(" \"vendor\": \"OpenAirInterface\"\n"); prnt(" }\n"); prnt(" },\n"); prnt(" \"O1-Operational\": {\n"); prnt(" \"frame-type\": \"%s\",\n", frame_type == TDD ? "tdd" : "fdd"); prnt(" \"band-number\": %ld,\n", band); prnt(" \"num-ues\": %d,\n", num_ues); prnt(" \"ues\": ["); PRINTLIST_i(num_ues, "%d", ue_stat[i].rnti); prnt("],\n"); prnt(" \"load\": %d,\n", load); prnt(" \"ues-thp\": ["); PRINTLIST_i(num_ues, "\n {\"rnti\": %d, \"dl\": %ld, \"ul\": %ld}", ue_stat[i].rnti, ue_stat[i].thr.dl, ue_stat[i].thr.ul); prnt("\n ]\n"); prnt(" }\n"); prnt("}\n"); prnt("OK\n"); NR_SCHED_UNLOCK(&mac->sched_lock); return 0; } static int read_long(const char *buf, const char *end, const char *id, long *val) { const char *curr = buf; while (isspace(*curr) && curr < end) // skip leading spaces curr++; int len = strlen(id); if (curr + len >= end) return -1; if (strncmp(curr, id, len) != 0) // check buf has id return -1; curr += len; while (isspace(*curr) && curr < end) // skip middle spaces curr++; if (curr >= end) return -1; int nread = sscanf(curr, "%ld", val); if (nread != 1) return -1; while (isdigit(*curr) && curr < end) // skip all digits read above curr++; if (curr > end) return -1; return curr - buf; } bool running = true; // in the beginning, the softmodem is started automatically static int set_config(char *buf, int debug, telnet_printfunc_t prnt) { if (!buf) ERROR_MSG_RET("need param: o1 config param1 val1 [param2 val2 ...]\n"); if (running) ERROR_MSG_RET("cannot set parameters while L1 is running\n"); const char *end = buf + strlen(buf); /* we need to update the following fields to change frequency and/or * bandwidth: * --gNBs.[0].servingCellConfigCommon.[0].absoluteFrequencySSB 620736 -> SSBFREQ * --gNBs.[0].servingCellConfigCommon.[0].dl_absoluteFrequencyPointA 620020 -> ARFCNDL * --gNBs.[0].servingCellConfigCommon.[0].dl_carrierBandwidth 51 -> BWDL * --gNBs.[0].servingCellConfigCommon.[0].initialDLBWPlocationAndBandwidth 13750 -> NUMRBS + STARTRB * --gNBs.[0].servingCellConfigCommon.[0].ul_carrierBandwidth 51 -> BWUL? * --gNBs.[0].servingCellConfigCommon.[0].initialULBWPlocationAndBandwidth 13750 -> ? */ int processed = 0; int pos = 0; long ssbfreq; processed = read_long(buf + pos, end, SSBFREQ, &ssbfreq); if (processed < 0) ERROR_MSG_RET("could not read " SSBFREQ " at index %d\n", pos + processed); pos += processed; prnt("setting " SSBFREQ ": %ld [len %d]\n", ssbfreq, pos); long arfcn; processed = read_long(buf + pos, end, ARFCNDL, &arfcn); if (processed < 0) ERROR_MSG_RET("could not read " ARFCNDL " at index %d\n", pos + processed); pos += processed; prnt("setting " ARFCNDL ": %ld [len %d]\n", arfcn, pos); long bwdl; processed = read_long(buf + pos, end, BWDL, &bwdl); if (processed < 0) ERROR_MSG_RET("could not read " BWDL " at index %d\n", pos + processed); pos += processed; prnt("setting " BWDL ": %ld [len %d]\n", bwdl, pos); long numrbs; processed = read_long(buf + pos, end, NUMRBS, &numrbs); if (processed < 0) ERROR_MSG_RET("could not read " NUMRBS " at index %d\n", pos + processed); pos += processed; prnt("setting " NUMRBS ": %ld [len %d]\n", numrbs, pos); long startrb; processed = read_long(buf + pos, end, STARTRB, &startrb); if (processed < 0) ERROR_MSG_RET("could not read " STARTRB " at index %d\n", pos + processed); pos += processed; prnt("setting " STARTRB ": %ld [len %d]\n", startrb, pos); int locationAndBandwidth = PRBalloc_to_locationandbandwidth0(numrbs, startrb, MAX_BWP_SIZE); prnt("inferred locationAndBandwidth: %d\n", locationAndBandwidth); prnt("OK\n"); return 0; } static int set_bwconfig(char *buf, int debug, telnet_printfunc_t prnt) { if (running) ERROR_MSG_RET("cannot set parameters while L1 is running\n"); if (!buf) ERROR_MSG_RET("need param: o1 bwconfig \n"); char *end = NULL; if (NULL != (end = strchr(buf, '\n'))) *end = 0; if (NULL != (end = strchr(buf, '\r'))) *end = 0; gNB_MAC_INST *mac = RC.nrmac[0]; NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon; NR_FrequencyInfoDL_t *frequencyInfoDL = scc->downlinkConfigCommon->frequencyInfoDL; NR_BWP_t *initialDL = &scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters; NR_FrequencyInfoUL_t *frequencyInfoUL = scc->uplinkConfigCommon->frequencyInfoUL; NR_BWP_t *initialUL = &scc->uplinkConfigCommon->initialUplinkBWP->genericParameters; if (strcmp(buf, "40") == 0) { *scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencySSB = 641280; frequencyInfoDL->absoluteFrequencyPointA = 640008; AssertFatal(frequencyInfoUL->absoluteFrequencyPointA == NULL, "only handle TDD\n"); frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth = 106; initialDL->locationAndBandwidth = 28875; frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth = 106; initialUL->locationAndBandwidth = 28875; get_softmodem_params()->threequarter_fs = 1; } else if (strcmp(buf, "20") == 0) { *scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencySSB = 641280; frequencyInfoDL->absoluteFrequencyPointA = 640596; AssertFatal(frequencyInfoUL->absoluteFrequencyPointA == NULL, "only handle TDD\n"); frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth = 51; initialDL->locationAndBandwidth = 13750; frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth = 51; initialUL->locationAndBandwidth = 13750; get_softmodem_params()->threequarter_fs = 0; } else if (strcmp(buf, "100") == 0) { *scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencySSB = 646668; frequencyInfoDL->absoluteFrequencyPointA = 643392; AssertFatal(frequencyInfoUL->absoluteFrequencyPointA == NULL, "only handle TDD\n"); frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth = 273; initialDL->locationAndBandwidth = 1099; frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth = 273; initialUL->locationAndBandwidth = 1099; get_softmodem_params()->threequarter_fs = 0; } else if (strcmp(buf, "60") == 0) { *scc->downlinkConfigCommon->frequencyInfoDL->absoluteFrequencySSB = 621984; frequencyInfoDL->absoluteFrequencyPointA = 620040; AssertFatal(frequencyInfoUL->absoluteFrequencyPointA == NULL, "only handle TDD\n"); frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth = 162; initialDL->locationAndBandwidth = 31624; frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth = 162; initialUL->locationAndBandwidth = 31624; get_softmodem_params()->threequarter_fs = 0; } else { ERROR_MSG_RET("unhandled option %s\n", buf); } free(RC.nrmac[0]->sched_ctrlSIB1); RC.nrmac[0]->sched_ctrlSIB1 = NULL; free_MIB_NR(mac->common_channels[0].mib); mac->common_channels[0].mib = get_new_MIB_NR(scc); const f1ap_served_cell_info_t *info = &mac->f1_config.setup_req->cell[0].info; nr_mac_configure_sib1(mac, &info->plmn, info->nr_cellid, *info->tac); prnt("OK\n"); return 0; } extern int stop_L1(module_id_t gnb_id); static int stop_modem(char *buf, int debug, telnet_printfunc_t prnt) { if (!running) ERROR_MSG_RET("cannot stop, nr-softmodem not running\n"); /* make UEs out of sync and wait 50ms to ensure no PUCCH is scheduled. After * a restart, the frame/slot numbers will be different, which "confuses" the * scheduler, which has many PUCCH structures filled with expected frame/slot * combinations that won't happen. */ const gNB_MAC_INST *mac = RC.nrmac[0]; UE_iterator((NR_UE_info_t **)mac->UE_info.connected_ue_list, it) { nr_mac_trigger_ul_failure(&it->UE_sched_ctrl, 1); } usleep(50000); stop_L1(0); running = false; prnt("OK\n"); return 0; } extern int start_L1L2(module_id_t gnb_id); static int start_modem(char *buf, int debug, telnet_printfunc_t prnt) { if (running) ERROR_MSG_RET("cannot start, nr-softmodem already running\n"); start_L1L2(0); running = true; prnt("OK\n"); return 0; } extern void du_clear_all_ue_states(); static int remove_mac_ues(char *buf, int debug, telnet_printfunc_t prnt) { du_clear_all_ue_states(); prnt("OK\n"); return 0; } static telnetshell_cmddef_t o1cmds[] = { {"stats", "", get_stats}, {"config", "[]", set_config}, {"bwconfig", "", set_bwconfig}, {"stop_modem", "", stop_modem}, {"start_modem", "", start_modem}, {"remove_mac_ues", "", remove_mac_ues}, {"", "", NULL}, }; static telnetshell_vardef_t o1vars[] = { {"", 0, 0, NULL} }; void add_o1_cmds(void) { add_telnetcmd("o1", o1vars, o1cmds); }