Add basic O1 telnet module
This adds a basic telnet module to support the O1 interface at the DU, to be used together with the oai/o1-adapter>. Concretely, this first version supports to - read/modify cell parameters - stop/start the L1
This commit is contained in:
@@ -75,7 +75,13 @@ add_library(telnetsrv_rrc MODULE telnetsrv_rrc.c)
|
|||||||
target_link_libraries(telnetsrv_rrc PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs)
|
target_link_libraries(telnetsrv_rrc PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs)
|
||||||
add_dependencies(telnetsrv telnetsrv_rrc)
|
add_dependencies(telnetsrv telnetsrv_rrc)
|
||||||
|
|
||||||
|
message(STATUS "Add CI specific telnet functions in libtelnetsrv_o1.so")
|
||||||
|
add_library(telnetsrv_o1 MODULE telnetsrv_o1.c)
|
||||||
|
target_link_libraries(telnetsrv_o1 PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs)
|
||||||
|
add_dependencies(telnetsrv telnetsrv_o1)
|
||||||
|
|
||||||
# all libraries should be written to root build dir
|
# all libraries should be written to root build dir
|
||||||
set_target_properties(telnetsrv telnetsrv_enb telnetsrv_5Gue telnetsrv_ci telnetsrv_ciUE telnetsrv_bearer telnetsrv_rrc
|
set_target_properties(telnetsrv telnetsrv_enb telnetsrv_5Gue telnetsrv_ci telnetsrv_ciUE
|
||||||
|
telnetsrv_bearer telnetsrv_rrc telnetsrv_o1
|
||||||
PROPERTIES LIBRARY_OUTPUT_DIRECTORY ../../..
|
PROPERTIES LIBRARY_OUTPUT_DIRECTORY ../../..
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
[[_TOC_]]
|
||||||
|
|
||||||
|
The telnet O1 module (`telnetsrv_o1.c`) can be used to perform some O1-related
|
||||||
|
actions (reading data, starting and stopping the nr-softmodem, reconfigurating
|
||||||
|
frequency and bandwidth).
|
||||||
|
|
||||||
|
# General usage
|
||||||
|
|
||||||
|
The usage is similar to the general telnet usage, but in short:
|
||||||
|
```
|
||||||
|
./build_oai --ninja -c --gNB --nrUE --build-lib telnetsrv
|
||||||
|
```
|
||||||
|
to build everything including the telnet library. Then, run the nr-softmodem
|
||||||
|
by activating telnet and loading the `o1` module:
|
||||||
|
```
|
||||||
|
./nr-softmodem -O <config> --telnetsrv --telnetsrv.shrmod o1
|
||||||
|
```
|
||||||
|
|
||||||
|
Afterwards, it should be possible to connect via telnet on localhost, port
|
||||||
|
9090. Use `help` to get help on the different command sections, and type e.g.
|
||||||
|
`o1 stats` to get statistics (more information further below):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ telnet 127.0.0.1 9090
|
||||||
|
Trying 127.0.0.1...
|
||||||
|
Connected to 127.0.0.1.
|
||||||
|
Escape character is '^]'.
|
||||||
|
|
||||||
|
softmodem_gnb> help
|
||||||
|
[...]
|
||||||
|
module 4 = o1:
|
||||||
|
o1 stats
|
||||||
|
o1 config ?
|
||||||
|
o1 stop_modem
|
||||||
|
o1 start_modem
|
||||||
|
[...]
|
||||||
|
softmodem_gnb> o1 stats
|
||||||
|
[...]
|
||||||
|
softmodem_gnb> exit
|
||||||
|
Connection closed by foreign host.
|
||||||
|
```
|
||||||
|
|
||||||
|
It also possible to send a command "directly from the command line", by piping
|
||||||
|
the command into netcat:
|
||||||
|
```
|
||||||
|
echo o1 stats | nc -N 127.0.0.1 9090
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that only one telnet client can be connected at a time.
|
||||||
|
|
||||||
|
# Get statistics
|
||||||
|
|
||||||
|
Use the `o1 stats` command. The output is in JSON format:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"o1-config": {
|
||||||
|
"BWP": {
|
||||||
|
"dl": [
|
||||||
|
{
|
||||||
|
"bwp3gpp:isInitialBwp": true,
|
||||||
|
"bwp3gpp:numberOfRBs": 106,
|
||||||
|
"bwp3gpp:startRB": 0,
|
||||||
|
"bwp3gpp:subCarrierSpacing": 30
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ul": [
|
||||||
|
{
|
||||||
|
"bwp3gpp:isInitialBwp": true,
|
||||||
|
"bwp3gpp:numberOfRBs": 106,
|
||||||
|
"bwp3gpp:startRB": 0,
|
||||||
|
"bwp3gpp:subCarrierSpacing": 30
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NRCELLDU": {
|
||||||
|
"nrcelldu3gpp:ssbFrequency": 641280,
|
||||||
|
"nrcelldu3gpp:arfcnDL": 640008,
|
||||||
|
"nrcelldu3gpp:bSChannelBwDL": 40,
|
||||||
|
"nrcelldu3gpp:arfcnUL": 640008,
|
||||||
|
"nrcelldu3gpp:bSChannelBwUL": 40,
|
||||||
|
"nrcelldu3gpp:nRPCI": 0,
|
||||||
|
"nrcelldu3gpp:nRTAC": 1,
|
||||||
|
"nrcelldu3gpp:mcc": "208",
|
||||||
|
"nrcelldu3gpp:mnc": "95",
|
||||||
|
"nrcelldu3gpp:sd": 16777215,
|
||||||
|
"nrcelldu3gpp:sst": 1
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"gnbId": 1,
|
||||||
|
"gnbName": "gNB-Eurecom-5GNRBox",
|
||||||
|
"vendor": "OpenAirInterface"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"O1-Operational": {
|
||||||
|
"frame-type": "tdd",
|
||||||
|
"band-number": 78,
|
||||||
|
"num-ues": 1,
|
||||||
|
"ues": [
|
||||||
|
6876
|
||||||
|
],
|
||||||
|
"load": 9,
|
||||||
|
"ues-thp": [
|
||||||
|
{
|
||||||
|
"rnti": 6876,
|
||||||
|
"dl": 3279,
|
||||||
|
"ul": 2725
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that no actual JSON engine is used, so no actual verification is done; it
|
||||||
|
is for convenience of the consumer. To verify, you can employ `jq`:
|
||||||
|
```
|
||||||
|
echo o1 stats | nc -N 127.0.0.1 9090 | awk '/^{$/, /^}$/' | jq .
|
||||||
|
```
|
||||||
|
(`awk`'s pattern matching makes that only everything between the first `{` and
|
||||||
|
its corresponding `}` is printed).
|
||||||
|
|
||||||
|
There are two sections:
|
||||||
|
1. `.o1-config` show some stats that map directly to the O1 Netconf model. Note
|
||||||
|
that only one MCC/MNC/SD/SST (each) are supported right now. Also, note that
|
||||||
|
as per 3GPP specifications, SD of value `0xffffff` (16777215 in decimal)
|
||||||
|
means "no SD". `bSChannelBwDL/UL` is reported in MHz.
|
||||||
|
2. `.O1-operational` output some statistics that do not map yet to any netconf
|
||||||
|
parameters, but that might be useful nevertheless for a consumer.
|
||||||
|
|
||||||
|
# Write a new configuration
|
||||||
|
|
||||||
|
Use `o1 config` to write a configuration:
|
||||||
|
```
|
||||||
|
echo o1 config nrcelldu3gpp:ssbFrequency 620736 nrcelldu3gpp:arfcnDL 620020 nrcelldu3gpp:bSChannelBwDL 51 bwp3gpp:numberOfRBs 51 bwp3gpp:startRB 0 | nc -N 127.0.0.1 9090
|
||||||
|
```
|
||||||
|
You have to pass the above parameters in exactly this order. The softmodem
|
||||||
|
needs to be stopped; it will pick up the new configuration when starting the
|
||||||
|
softmodem again.
|
||||||
|
|
||||||
|
Note that you cannot switch three-quarter sampling for this as of now.
|
||||||
|
|
||||||
|
For values of the configuration, refer to the next section.
|
||||||
|
|
||||||
|
# Use hardcoded configuration
|
||||||
|
|
||||||
|
Use `o1 bwconfig` to write a hard-coded configuration for 20 or 40 MHz cells:
|
||||||
|
```
|
||||||
|
echo o1 bwconfig 20 | nc -N 127.0.0.1 9090
|
||||||
|
echo o1 bwconfig 40 | nc -N 127.0.0.1 9090
|
||||||
|
```
|
||||||
|
|
||||||
|
The softmodem needs to be stopped; it will pick up the new configuration when
|
||||||
|
starting the softmodem again.
|
||||||
|
|
||||||
|
Use `o1 stats` to see which configurations are set by these commands for the
|
||||||
|
parameters `nrcelldu3gpp:ssbFrequency`, `nrcelldu3gpp:arfcnDL`,
|
||||||
|
`nrcelldu3gpp:arfcnUL`, `nrcelldu3gpp:bSChannelBwDL`,
|
||||||
|
`nrcelldu3gpp:bSChannelBwUL`, and `bwp3gpp:numberOfRBsbwp3gpp:startRB`.
|
||||||
|
Furthermore, for 20MHz, it *disables* three-quarter sampling, whereas it
|
||||||
|
*enables* three-quarter sampling for 40MHz.
|
||||||
|
|
||||||
|
# Restart the softmodem
|
||||||
|
|
||||||
|
Use `o1 stop_modem` to stop the `nr-softmodem`. To restart the softmodem, use
|
||||||
|
`o1 start_modem`:
|
||||||
|
```
|
||||||
|
echo o1 stop_modem | nc -N 127.0.0.1 9090
|
||||||
|
echo o1 start_modem | nc -N 127.0.0.1 9090
|
||||||
|
```
|
||||||
|
|
||||||
|
In fact, stopping terminates all L1 threads. It will be as if the softmodem
|
||||||
|
"freezes", and no periodical output of statistics will occur (the O1 telnet
|
||||||
|
interface will still work, though). Starting again will "defreeze" the
|
||||||
|
softmodem.
|
||||||
|
|
||||||
|
Upon restart, the DU sends a gNB-DU configuration update to the CU to inform it
|
||||||
|
about the updated configuration. Therefore, this also works in F1.
|
||||||
@@ -3,5 +3,6 @@ The oai embedded telnet server is an optional monitoring and debugging tool. It
|
|||||||
* [Using the telnet server](telnetusage.md)
|
* [Using the telnet server](telnetusage.md)
|
||||||
* [Adding commands to the oai telnet server](telnetaddcmd.md)
|
* [Adding commands to the oai telnet server](telnetaddcmd.md)
|
||||||
* [telnet server architecture ](telnetarch.md)
|
* [telnet server architecture ](telnetarch.md)
|
||||||
|
* [on the telnet O1 module](telneto1.md)
|
||||||
|
|
||||||
[oai Wikis home](https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/home)
|
[oai Wikis home](https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/home)
|
||||||
|
|||||||
@@ -0,0 +1,391 @@
|
|||||||
|
/*
|
||||||
|
* 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 <sys/types.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#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/RRC/NR/nr_rrc_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 mac_stats_t *stat = &mac->mac_stats;
|
||||||
|
static mac_stats_t last = {0};
|
||||||
|
int diff_used = stat->used_prb_aggregate - last.used_prb_aggregate;
|
||||||
|
int diff_total = stat->total_prb_aggregate - last.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.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 <BW>\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 {
|
||||||
|
ERROR_MSG_RET("unhandled option %s\n", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(RC.nrmac[0]->sched_ctrlCommon);
|
||||||
|
RC.nrmac[0]->sched_ctrlCommon = 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.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static telnetshell_cmddef_t o1cmds[] = {
|
||||||
|
{"stats", "", get_stats},
|
||||||
|
{"config", "[]", set_config},
|
||||||
|
{"bwconfig", "", set_bwconfig},
|
||||||
|
{"stop_modem", "", stop_modem},
|
||||||
|
{"start_modem", "", start_modem},
|
||||||
|
{"", "", NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
static telnetshell_vardef_t o1vars[] = {
|
||||||
|
{"", 0, 0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
void add_o1_cmds(void) {
|
||||||
|
add_telnetcmd("o1", o1vars, o1cmds);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user