From 489969722e3120d1b1265c4e9b4fe406edfc8a01 Mon Sep 17 00:00:00 2001 From: Francisco Paisana Date: Tue, 7 Jul 2020 17:29:57 +0100 Subject: [PATCH] implemented phy controller FSM to perform cell selection & search. The controller also tracks the phy sync state --- lib/include/srslte/common/fsm.h | 3 + lib/include/srslte/test/ue_test_interfaces.h | 24 +++ srsue/hdr/stack/rrc/phy_controller.h | 160 +++++++++++++++++++ srsue/src/stack/rrc/CMakeLists.txt | 2 +- srsue/src/stack/rrc/phy_controller.cc | 149 +++++++++++++++++ srsue/test/upper/CMakeLists.txt | 4 + srsue/test/upper/rrc_phy_ctrl_test.cc | 123 ++++++++++++++ 7 files changed, 464 insertions(+), 1 deletion(-) create mode 100644 srsue/hdr/stack/rrc/phy_controller.h create mode 100644 srsue/src/stack/rrc/phy_controller.cc create mode 100644 srsue/test/upper/rrc_phy_ctrl_test.cc diff --git a/lib/include/srslte/common/fsm.h b/lib/include/srslte/common/fsm.h index 38ccd51ee..5b054334e 100644 --- a/lib/include/srslte/common/fsm.h +++ b/lib/include/srslte/common/fsm.h @@ -679,6 +679,9 @@ private: ProcFSM* proc_ptr = nullptr; }; +template +using event_callback = std::function; + } // namespace srslte #endif // SRSLTE_FSM_H diff --git a/lib/include/srslte/test/ue_test_interfaces.h b/lib/include/srslte/test/ue_test_interfaces.h index 18c31fa64..f0cc568de 100644 --- a/lib/include/srslte/test/ue_test_interfaces.h +++ b/lib/include/srslte/test/ue_test_interfaces.h @@ -65,6 +65,30 @@ public: void write_pdu_mch(uint32_t lcid, uint8_t* payload, uint32_t nof_bytes) override {} }; +class phy_dummy_interface : public phy_interface_rrc_lte +{ + void + set_config(srslte::phy_cfg_t& config, uint32_t cc_idx = 0, uint32_t earfcn = 0, srslte_cell_t* cell_info = nullptr) + {} + void set_config_tdd(srslte_tdd_config_t& tdd_config) {} + void set_config_mbsfn_sib2(srslte::mbsfn_sf_cfg_t* cfg_list, uint32_t nof_cfgs) {} + void set_config_mbsfn_sib13(const srslte::sib13_t& sib13) {} + void set_config_mbsfn_mcch(const srslte::mcch_msg_t& mcch) {} + + /* Measurements interface */ + void set_cells_to_meas(uint32_t earfcn, const std::set& pci) {} + void meas_stop() {} + + /* Cell search and selection procedures */ + cell_search_ret_t cell_search(phy_cell_t* cell) { return {}; } + bool cell_select(const phy_cell_t* cell = nullptr) { return true; } + bool cell_is_camping() { return false; } + + void reset() {} + + void enable_pregen_signals(bool enable) {} +}; + } // namespace srsue #endif // SRSUE_DUMMY_CLASSES_H diff --git a/srsue/hdr/stack/rrc/phy_controller.h b/srsue/hdr/stack/rrc/phy_controller.h new file mode 100644 index 000000000..0687096a4 --- /dev/null +++ b/srsue/hdr/stack/rrc/phy_controller.h @@ -0,0 +1,160 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSLTE_PHY_CONTROLLER_H +#define SRSLTE_PHY_CONTROLLER_H + +#include "srslte/common/fsm.h" +#include "srslte/common/logmap.h" +#include "srslte/interfaces/ue_interfaces.h" + +namespace srsue { + +class phy_controller : public srslte::fsm_t +{ + using phy_cell_t = phy_interface_rrc_lte::phy_cell_t; + using cell_search_ret_t = phy_interface_rrc_lte::cell_search_ret_t; + +public: + static const uint32_t wait_sync_timeout_ms = 50; + + // events + struct cell_srch_res { + cell_search_ret_t cs_ret; + phy_cell_t found_cell; + }; + using cell_sel_res = bool; + struct cell_sel_cmd { + phy_cell_t phy_cell; + srslte::event_callback callback; + }; + using cell_search_cmd = srslte::event_callback; + struct in_sync_ev {}; + struct out_sync_ev {}; + struct timeout_ev {}; + + explicit phy_controller(phy_interface_rrc_lte* phy_, stack_interface_rrc* stack_); + + // PHY procedures interfaces + bool start_cell_select(const phy_cell_t& phy_cell, const srslte::event_callback& on_complete); + bool start_cell_search(const srslte::event_callback& on_complete); + bool cell_search_completed(cell_search_ret_t cs_ret, phy_cell_t found_cell); + bool cell_selection_completed(bool outcome); + void in_sync() { trigger(in_sync_ev{}); } + void out_sync() { trigger(out_sync_ev{}); } + + // state getters + bool cell_is_camping() { return phy->cell_is_camping(); } + bool is_in_sync() const { return is_in_state(); } + +private: + phy_interface_rrc_lte* phy = nullptr; + stack_interface_rrc* stack = nullptr; + +protected: + // states + struct unknown_st {}; + struct in_sync_st {}; + struct out_sync_st {}; + struct selecting_cell : public subfsm_t { + struct timeout_ev {}; + + struct wait_result {}; + struct wait_in_sync { + void enter(selecting_cell* f, const cell_sel_res& ev); + }; + + explicit selecting_cell(phy_controller* parent_); + void enter(phy_controller* f, const cell_sel_cmd& ev); + void exit(phy_controller* f); + + srslte::timer_handler::unique_timer wait_in_sync_timer; + phy_cell_t target_cell = {}; + cell_sel_res result = {}; + srslte::event_callback csel_callback; + + protected: + // guard functions + bool is_cell_selected(wait_result& s, const cell_sel_res& ev) { return ev; } + + // event handlers + void set_success(wait_in_sync& s, const in_sync_ev& ev) { result = true; } + + state_list states{this}; + + // clang-format off + using c = selecting_cell; + using transitions = transition_table< + // Start Target Event Action Guard + // +----------------+---------------+--------------+------------------+----------------------+ + row< wait_result, wait_in_sync, cell_sel_res, nullptr, &c::is_cell_selected >, + row< wait_result, unknown_st, cell_sel_res >, + // +----------------+---------------+--------------+------------------+----------------------+ + row< wait_in_sync, in_sync_st, in_sync_ev, &c::set_success >, + row< wait_in_sync, out_sync_st, timeout_ev > + // +----------------+---------------+--------------+------------------+----------------------+ + >; + // clang-format on + }; + struct searching_cell { + void enter(phy_controller* f, const cell_search_cmd& cmd); + + std::vector > csearch_callbacks; + }; + state_list states{this, + unknown_st{}, + in_sync_st{}, + out_sync_st{}, + searching_cell{}, + selecting_cell{this}}; + + // event handlers + void handle_cell_search_res(searching_cell& s, const cell_srch_res& result); + void share_cell_search_res(searching_cell& s, const cell_search_cmd& cmd); + + // clang-format off + using c = phy_controller; + using transitions = transition_table< + // Start Target Event Action Guard + // +----------------+-----------------+------------------+------------------------------+---------------------+ + row< unknown_st, selecting_cell, cell_sel_cmd >, + row< unknown_st, searching_cell, cell_search_cmd >, + row< unknown_st, in_sync_st, in_sync_ev >, + row< unknown_st, out_sync_st, out_sync_ev >, + // +----------------+-----------------+------------------+------------------------------+---------------------+ + row< in_sync_st, selecting_cell, cell_sel_cmd >, + row< in_sync_st, searching_cell, cell_search_cmd >, + row< in_sync_st, out_sync_st, out_sync_ev >, + // +----------------+-----------------+------------------+------------------------------+---------------------+ + row< out_sync_st, selecting_cell, cell_sel_cmd >, + row< out_sync_st, searching_cell, cell_search_cmd >, + row< out_sync_st, in_sync_st, in_sync_ev >, + // +----------------+-----------------+------------------+------------------------------+---------------------+ + row< searching_cell, unknown_st, cell_srch_res, &c::handle_cell_search_res >, + upd< searching_cell, cell_search_cmd, &c::share_cell_search_res > + // +----------------+-----------------+------------------+------------------------------+---------------------+ + >; + // clang-format on +}; + +} // namespace srsue + +#endif // SRSLTE_PHY_CONTROLLER_H diff --git a/srsue/src/stack/rrc/CMakeLists.txt b/srsue/src/stack/rrc/CMakeLists.txt index 2f5ac82b3..9418cf09a 100644 --- a/srsue/src/stack/rrc/CMakeLists.txt +++ b/srsue/src/stack/rrc/CMakeLists.txt @@ -18,7 +18,7 @@ # and at http://www.gnu.org/licenses/. # -set(SOURCES rrc.cc rrc_procedures.cc rrc_meas.cc rrc_cell.cc) +set(SOURCES rrc.cc rrc_procedures.cc rrc_meas.cc rrc_cell.cc phy_controller.cc) add_library(srsue_rrc STATIC ${SOURCES}) if(ENABLE_5GNR) diff --git a/srsue/src/stack/rrc/phy_controller.cc b/srsue/src/stack/rrc/phy_controller.cc new file mode 100644 index 000000000..90e37b230 --- /dev/null +++ b/srsue/src/stack/rrc/phy_controller.cc @@ -0,0 +1,149 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srsue/hdr/stack/rrc/phy_controller.h" + +namespace srsue { + +std::string to_string(const phy_interface_rrc_lte::phy_cell_t& cell) +{ + char buffer[128]; + snprintf(buffer, sizeof(buffer), "{pci=%d, dl_earfcn=%d}", cell.pci, cell.earfcn); + return buffer; +} + +phy_controller::phy_controller(srsue::phy_interface_rrc_lte* phy_, srsue::stack_interface_rrc* stack_) : + base_t(srslte::log_ref{"RRC"}), + phy(phy_), + stack(stack_) +{} + +/************************************** + * PHY Cell Select Procedure + *************************************/ + +bool phy_controller::start_cell_select(const phy_cell_t& phy_cell, + const srslte::event_callback& on_complete) +{ + if (not trigger(cell_sel_cmd{phy_cell, on_complete})) { + log_h->warning("Failed to launch cell selection\n"); + return false; + } + return true; +} + +phy_controller::selecting_cell::selecting_cell(phy_controller* parent_) : nested_fsm_t(parent_) +{ + wait_in_sync_timer = parent_fsm()->stack->get_unique_timer(); +} + +void phy_controller::selecting_cell::enter(phy_controller* f, const cell_sel_cmd& ev) +{ + target_cell = ev.phy_cell; + csel_callback = ev.callback; + result = false; + + f->log_h->info("Starting \"%s\" for pci=%d, earfcn=%d\n", + srslte::get_type_name(*this).c_str(), + target_cell.pci, + target_cell.earfcn); + f->stack->start_cell_select(&target_cell); +} + +void phy_controller::selecting_cell::exit(phy_controller* f) +{ + wait_in_sync_timer.stop(); + + if (result) { + log_h->info("Cell %s successfully selected\n", to_string(target_cell).c_str()); + } else { + log_h->warning("Failed to select cell %s\n", to_string(target_cell).c_str()); + } + + // Signal result back to FSM that called cell selection + csel_callback(result); +} + +void phy_controller::selecting_cell::wait_in_sync::enter(selecting_cell* f, const cell_sel_res& ev) +{ + f->wait_in_sync_timer.set(wait_sync_timeout_ms, [f](uint32_t tid) { f->trigger(timeout_ev{}); }); + f->wait_in_sync_timer.run(); +} + +/************************************** + * PHY Cell Search Procedure + *************************************/ + +//! Searches for a cell in the current frequency and retrieves SIB1 if not retrieved yet +bool phy_controller::start_cell_search(const srslte::event_callback& on_complete) +{ + if (not trigger(on_complete)) { + log_h->warning("Failed to launch cell search\n"); + return false; + } + return true; +} + +bool phy_controller::cell_search_completed(cell_search_ret_t cs_ret, phy_cell_t found_cell) +{ + return trigger(cell_srch_res{cs_ret, found_cell}); +} + +bool phy_controller::cell_selection_completed(bool outcome) +{ + return trigger(outcome); +} + +void phy_controller::searching_cell::enter(phy_controller* f, const cell_search_cmd& cmd) +{ + f->log_h->info("Initiated Cell search\n"); + csearch_callbacks.emplace_back(cmd); + f->stack->start_cell_search(); +} + +void phy_controller::handle_cell_search_res(searching_cell& s, const cell_srch_res& result) +{ + switch (result.cs_ret.found) { + case cell_search_ret_t::CELL_FOUND: + log_h->info("PHY cell search completed. Found cell %s\n", to_string(result.found_cell).c_str()); + break; + case cell_search_ret_t::CELL_NOT_FOUND: + log_h->warning("PHY cell search completed. No cells found\n"); + break; + default: + log_h->error("Invalid cell search result\n"); + // TODO: check what errors can happen (currently not handled in our code) + } + + // Signal back completion + for (auto& f : s.csearch_callbacks) { + f(result); + } + s.csearch_callbacks.clear(); +} + +void phy_controller::share_cell_search_res(searching_cell& s, const cell_search_cmd& callback) +{ + log_h->info("Cell Search already running. Re-utilizing result.\n"); + s.csearch_callbacks.emplace_back(callback); +} + +} // namespace srsue diff --git a/srsue/test/upper/CMakeLists.txt b/srsue/test/upper/CMakeLists.txt index 4a175770e..7c8e29c7a 100644 --- a/srsue/test/upper/CMakeLists.txt +++ b/srsue/test/upper/CMakeLists.txt @@ -43,6 +43,10 @@ add_executable(tft_test tft_test.cc) target_link_libraries(tft_test srsue_upper srslte_upper srslte_phy) add_test(tft_test tft_test) +add_executable(rrc_phy_ctrl_test rrc_phy_ctrl_test.cc) +target_link_libraries(rrc_phy_ctrl_test srslte_common srsue_rrc) +add_test(rrc_phy_ctrl_test rrc_phy_ctrl_test) + ######################################################################## # Option to run command after build (useful for remote builds) ######################################################################## diff --git a/srsue/test/upper/rrc_phy_ctrl_test.cc b/srsue/test/upper/rrc_phy_ctrl_test.cc new file mode 100644 index 000000000..bc4c61731 --- /dev/null +++ b/srsue/test/upper/rrc_phy_ctrl_test.cc @@ -0,0 +1,123 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srslte/common/test_common.h" +#include "srslte/test/ue_test_interfaces.h" +#include "srsue/hdr/stack/rrc/phy_controller.h" + +namespace srsue { + +int test_phy_ctrl_fsm() +{ + srslte::log_ref test_log{"TEST"}; + stack_test_dummy stack; + phy_dummy_interface phy; + phy_controller phy_ctrl{&phy, &stack}; + + TESTASSERT(not phy_ctrl.is_in_sync()); + TESTASSERT(not phy_ctrl.cell_is_camping()); + + // TEST: Sync event changes phy controller state + phy_ctrl.in_sync(); + TESTASSERT(phy_ctrl.is_in_sync()); + phy_ctrl.out_sync(); + TESTASSERT(not phy_ctrl.is_in_sync()); + phy_ctrl.in_sync(); + TESTASSERT(phy_ctrl.is_in_sync()); + + // TEST: Correct initiation of Cell Search state + bool csearch_res_present = false; + phy_controller::cell_srch_res csearch_res = {}; + auto cell_sel_callback = [&csearch_res_present, &csearch_res](const phy_controller::cell_srch_res& result) { + csearch_res_present = true; + csearch_res = result; + }; + TESTASSERT(phy_ctrl.start_cell_search(cell_sel_callback)); + TESTASSERT(not phy_ctrl.is_in_sync()); + + // TEST: Cell Search only listens to a cell search result event and calls provided callback on completion + phy_ctrl.in_sync(); + TESTASSERT(not phy_ctrl.is_in_sync()); + phy_ctrl.out_sync(); + TESTASSERT(not phy_ctrl.is_in_sync()); + TESTASSERT(phy_ctrl.current_state_name() == "searching_cell"); + phy_interface_rrc_lte::cell_search_ret_t cs_ret; + cs_ret.found = phy_interface_rrc_lte::cell_search_ret_t::CELL_FOUND; + phy_interface_rrc_lte::phy_cell_t found_cell; + found_cell.pci = 1; + found_cell.earfcn = 2; + phy_ctrl.cell_search_completed(cs_ret, found_cell); + TESTASSERT(phy_ctrl.current_state_name() != "searching_cell"); + TESTASSERT(csearch_res_present); + TESTASSERT(csearch_res.cs_ret.found == cs_ret.found); + TESTASSERT(csearch_res.found_cell.pci == found_cell.pci); + TESTASSERT(csearch_res.found_cell.earfcn == found_cell.earfcn); + phy_ctrl.in_sync(); + TESTASSERT(phy_ctrl.is_in_sync()); + phy_ctrl.out_sync(); + + // TEST: Correct initiation of Cell Select state + int cell_select_success = -1; + auto csel_callback = [&cell_select_success](const bool& res) { cell_select_success = res ? 1 : 0; }; + phy_ctrl.start_cell_select(found_cell, csel_callback); + TESTASSERT(not phy_ctrl.is_in_sync()); + TESTASSERT(phy_ctrl.current_state_name() == "selecting_cell"); + + // TEST: Cell Selection state ignores events other than the cell selection result, and callback is called + phy_ctrl.in_sync(); + TESTASSERT(not phy_ctrl.is_in_sync()); + phy_ctrl.cell_selection_completed(true); + // Note: Still in cell selection, but now waiting for the first in_sync + TESTASSERT(phy_ctrl.current_state_name() == "selecting_cell"); + TESTASSERT(not phy_ctrl.is_in_sync()); + TESTASSERT(cell_select_success < 0); + phy_ctrl.in_sync(); + TESTASSERT(phy_ctrl.is_in_sync()); + TESTASSERT(phy_ctrl.current_state_name() != "selecting_cell"); + TESTASSERT(cell_select_success == 1); + + // TEST: Cell Selection with timeout being reached + cell_select_success = -1; + phy_ctrl.start_cell_select(found_cell, csel_callback); + TESTASSERT(not phy_ctrl.is_in_sync()); + phy_ctrl.cell_selection_completed(true); + TESTASSERT(phy_ctrl.current_state_name() == "selecting_cell"); + TESTASSERT(cell_select_success < 0); + + for (uint32_t i = 0; i < phy_controller::wait_sync_timeout_ms; ++i) { + TESTASSERT(phy_ctrl.current_state_name() == "selecting_cell"); + stack.run_tti(); + } + TESTASSERT(phy_ctrl.current_state_name() != "selecting_cell"); + TESTASSERT(cell_select_success == 0); + + test_log->info("Finished RRC PHY controller test successfully\n"); + return SRSLTE_SUCCESS; +} + +} // namespace srsue + +int main() +{ + srslte::logmap::set_default_log_level(srslte::LOG_LEVEL_INFO); + + TESTASSERT(srsue::test_phy_ctrl_fsm() == SRSLTE_SUCCESS); +}