From aba506e5e3669a10e3e4e99a6fdf133ead1b5360 Mon Sep 17 00:00:00 2001
From: Robert Schmidt <robert.schmidt@openairinterface.org>
Date: Thu, 5 Oct 2023 08:53:12 +0200
Subject: [PATCH] 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
---
 common/utils/telnetsrv/CMakeLists.txt   |   8 +-
 common/utils/telnetsrv/DOC/telneto1.md  | 176 +++++++++++
 common/utils/telnetsrv/DOC/telnetsrv.md |   1 +
 common/utils/telnetsrv/telnetsrv_o1.c   | 391 ++++++++++++++++++++++++
 4 files changed, 575 insertions(+), 1 deletion(-)
 create mode 100644 common/utils/telnetsrv/DOC/telneto1.md
 create mode 100644 common/utils/telnetsrv/telnetsrv_o1.c

diff --git a/common/utils/telnetsrv/CMakeLists.txt b/common/utils/telnetsrv/CMakeLists.txt
index a7da081f093..5f190edc021 100644
--- a/common/utils/telnetsrv/CMakeLists.txt
+++ b/common/utils/telnetsrv/CMakeLists.txt
@@ -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)
 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
-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 ../../..
 )
diff --git a/common/utils/telnetsrv/DOC/telneto1.md b/common/utils/telnetsrv/DOC/telneto1.md
new file mode 100644
index 00000000000..4c6b9a3b4d7
--- /dev/null
+++ b/common/utils/telnetsrv/DOC/telneto1.md
@@ -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.
diff --git a/common/utils/telnetsrv/DOC/telnetsrv.md b/common/utils/telnetsrv/DOC/telnetsrv.md
index 11c6cf071fd..68051e7df82 100644
--- a/common/utils/telnetsrv/DOC/telnetsrv.md
+++ b/common/utils/telnetsrv/DOC/telnetsrv.md
@@ -3,5 +3,6 @@ The oai embedded telnet server is an optional monitoring and debugging tool. It
 * [Using the telnet server](telnetusage.md)
 * [Adding commands to the oai telnet server](telnetaddcmd.md)
 * [telnet server architecture ](telnetarch.md)
+* [on the telnet O1 module](telneto1.md)
 
 [oai Wikis home](https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/home)
diff --git a/common/utils/telnetsrv/telnetsrv_o1.c b/common/utils/telnetsrv/telnetsrv_o1.c
new file mode 100644
index 00000000000..36cffdf18dd
--- /dev/null
+++ b/common/utils/telnetsrv/telnetsrv_o1.c
@@ -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);
+}
-- 
GitLab