/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 | } |