Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ocudu/lib/cu_cp/logical_cell_manager.cpp
Line
Count
Source
1
// SPDX-FileCopyrightText: Copyright (C) 2021-2026 Software Radio Systems Limited
2
// SPDX-License-Identifier: BSD-3-Clause-Open-MPI
3
// Portions of this file may implement 3GPP specifications, which may be subject to additional licensing requirements.
4
5
#include "logical_cell_manager.h"
6
7
using namespace ocudu;
8
using namespace ocudu::ocucp;
9
10
logical_cell_manager::logical_cell_manager(span<const cu_cp_logical_cell_config> declared_cells) :
11
1
  any_cells_declared(not declared_cells.empty()), logger(ocudulog::fetch_basic_logger("CU-CP"))
12
1
{
13
1
  for (const cu_cp_logical_cell_config& cell_cfg : declared_cells) {
14
0
    logical_cell& cell = cells[cell_cfg.nci];
15
0
    cell.nci           = cell_cfg.nci;
16
0
    cell.admin_state   = cell_cfg.admin_state;
17
0
    cell.barred        = cell_cfg.barred;
18
0
    logger.debug("Declared logical cell nci={:#x} admin_state={} barred={}",
19
0
                 cell_cfg.nci.value(),
20
0
                 to_string(cell_cfg.admin_state),
21
0
                 cell_cfg.barred);
22
0
  }
23
1
}
24
25
const logical_cell* logical_cell_manager::find_cell(nr_cell_identity nci) const
26
0
{
27
0
  auto it = cells.find(nci);
28
0
  return it != cells.end() ? &it->second : nullptr;
29
0
}
30
31
std::optional<cell_admin_state> logical_cell_manager::set_admin_state(nr_cell_identity nci, cell_admin_state state)
32
0
{
33
0
  auto it = cells.find(nci);
34
0
  if (it == cells.end()) {
35
0
    logger.warning("Cannot set admin_state={} for unknown logical cell nci={:#x}", to_string(state), nci.value());
36
0
    return std::nullopt;
37
0
  }
38
0
  cell_admin_state prev  = it->second.admin_state;
39
0
  it->second.admin_state = state;
40
0
  if (prev != state) {
41
0
    logger.info("Logical cell nci={:#x} admin_state: {} -> {}", nci.value(), to_string(prev), to_string(state));
42
0
  }
43
0
  return prev;
44
0
}
45
46
std::optional<bool> logical_cell_manager::set_barred(nr_cell_identity nci, bool barred)
47
0
{
48
0
  auto it = cells.find(nci);
49
0
  if (it == cells.end()) {
50
0
    logger.warning("Cannot set barred={} for unknown logical cell nci={:#x}", barred, nci.value());
51
0
    return std::nullopt;
52
0
  }
53
0
  bool prev                        = it->second.barred;
54
0
  it->second.barred                = barred;
55
0
  it->second.barred_by_failed_stop = false;
56
0
  if (prev != barred) {
57
0
    logger.info("Logical cell nci={:#x} barred: {} -> {}", nci.value(), prev, barred);
58
0
  }
59
0
  return prev;
60
0
}
61
62
bool logical_cell_manager::record_failed_stop_bar(nr_cell_identity nci)
63
0
{
64
0
  auto it = cells.find(nci);
65
0
  if (it == cells.end()) {
66
0
    logger.warning("Cannot record the failed-stop bar for unknown logical cell nci={:#x}", nci.value());
67
0
    return false;
68
0
  }
69
0
  it->second.barred                = true;
70
0
  it->second.barred_by_failed_stop = true;
71
0
  logger.warning("Cell nci={:#x} remains barred after the failed graceful stop. Recording the barred state",
72
0
                 nci.value());
73
0
  return true;
74
0
}
75
76
std::optional<cell_operational_state> logical_cell_manager::set_operational_state(nr_cell_identity       nci,
77
                                                                                  cell_operational_state state)
78
0
{
79
0
  auto it = cells.find(nci);
80
0
  if (it == cells.end()) {
81
0
    logger.warning("Cannot set operational_state={} for unknown logical cell nci={:#x}", to_string(state), nci.value());
82
0
    return std::nullopt;
83
0
  }
84
0
  cell_operational_state prev  = it->second.operational_state;
85
0
  it->second.operational_state = state;
86
0
  if (prev != state) {
87
0
    logger.info("Logical cell nci={:#x} operational_state: {} -> {}", nci.value(), to_string(prev), to_string(state));
88
0
  }
89
0
  if (state == cell_operational_state::disabled && it->second.barred_by_failed_stop) {
90
    // The deactivation took effect: the on-air bar the failed stop left behind is gone with the cell.
91
0
    it->second.barred                = false;
92
0
    it->second.barred_by_failed_stop = false;
93
0
    logger.info("Logical cell nci={:#x} failed-stop bar cleared (cell deactivated)", nci.value());
94
0
  }
95
96
0
  return prev;
97
0
}
98
99
const logical_cell& logical_cell_manager::realize_cell(nr_cell_identity nci, cu_cp_du_index_t du_index)
100
0
{
101
0
  auto it = cells.find(nci);
102
0
  if (it == cells.end()) {
103
0
    it             = cells.emplace(nci, logical_cell{}).first;
104
0
    it->second.nci = nci;
105
0
    if (any_cells_declared) {
106
      // Cells were declared in configuration, so the declared set acts as the activation whitelist: a
107
      // reported cell outside it comes up locked, staying deactivated until an explicit cell_unlock command
108
      // or a configuration declaration.
109
0
      it->second.admin_state = cell_admin_state::locked;
110
0
      logger.warning("Cell nci={:#x} reported by a DU is not declared in the CU-CP configuration: keeping it "
111
0
                     "locked (not activated)",
112
0
                     nci.value());
113
0
    } else {
114
      // No cells declared in configuration: create a dynamic logical cell with default intent, so
115
      // undeclared deployments keep the pre-logical-cell behaviour.
116
0
      logger.debug("Added dynamic logical cell nci={:#x}", nci.value());
117
0
    }
118
0
  }
119
120
0
  logical_cell& cell = it->second;
121
0
  if (cell.realized && cell.du_index != du_index) {
122
0
    logger.warning("Logical cell nci={:#x} reported by du={} is already realized by du={}",
123
0
                   nci.value(),
124
0
                   fmt::underlying(du_index),
125
0
                   fmt::underlying(cell.du_index));
126
0
  }
127
0
  cell.realized = true;
128
0
  cell.du_index = du_index;
129
130
0
  logger.info("Logical cell nci={:#x} realized by du={} (admin_state={} barred={})",
131
0
              nci.value(),
132
0
              fmt::underlying(du_index),
133
0
              to_string(cell.admin_state),
134
0
              cell.barred);
135
136
0
  return cell;
137
0
}
138
139
void logical_cell_manager::derealize_du_cells(cu_cp_du_index_t du_index)
140
0
{
141
0
  for (auto& [nci, cell] : cells) {
142
0
    if (cell.realized && cell.du_index == du_index) {
143
0
      cell.realized          = false;
144
0
      cell.du_index          = cu_cp_du_index_t::invalid;
145
0
      cell.operational_state = cell_operational_state::disabled;
146
0
      if (cell.admin_state == cell_admin_state::shutting_down) {
147
        // A graceful stop cannot complete once the DU is gone, and the cell is off the air either way:
148
        // resolve the transient to locked so the operator intent behind the stop is kept.
149
0
        cell.admin_state = cell_admin_state::locked;
150
0
        logger.info("Logical cell nci={:#x} admin_state: shutting_down -> locked (du={} removed mid-stop)",
151
0
                    nci.value(),
152
0
                    fmt::underlying(du_index));
153
0
      }
154
0
      if (cell.barred_by_failed_stop) {
155
        // The bar a failed stop left on the air is gone with the DU; only operator intent survives.
156
0
        cell.barred                = false;
157
0
        cell.barred_by_failed_stop = false;
158
0
        logger.info(
159
0
            "Logical cell nci={:#x} failed-stop bar cleared (du={} removed)", nci.value(), fmt::underlying(du_index));
160
0
      }
161
162
0
      logger.info("Logical cell nci={:#x} de-realized (du={} removed). Operator intent kept (admin_state={} "
163
0
                  "barred={})",
164
0
                  nci.value(),
165
0
                  fmt::underlying(du_index),
166
0
                  to_string(cell.admin_state),
167
0
                  cell.barred);
168
0
    }
169
0
  }
170
0
}