From c9d3b610383e6d59f36c453deaf59e02cfb4be2e Mon Sep 17 00:00:00 2001 From: Francisco Paisana Date: Thu, 20 Jun 2019 16:33:03 +0100 Subject: [PATCH] added resumable procedures to make RRC and NAS non-blocking --- lib/include/srslte/common/stack_procedure.h | 317 +++++++ .../srslte/interfaces/rrc_interface_types.h | 1 + lib/include/srslte/interfaces/ue_interfaces.h | 89 +- lib/include/srslte/upper/pdcp.h | 2 +- lib/test/common/CMakeLists.txt | 4 +- lib/test/common/stack_procedure_test.cc | 312 +++++++ srsue/hdr/stack/rrc/rrc.h | 80 +- srsue/hdr/stack/rrc/rrc_procedures.h | 198 ++++ srsue/hdr/stack/upper/nas.h | 63 +- srsue/src/stack/rrc/CMakeLists.txt | 3 +- srsue/src/stack/rrc/rrc.cc | 846 +++++------------- srsue/src/stack/rrc/rrc_procedures.cc | 795 ++++++++++++++++ srsue/src/stack/ue_stack_lte.cc | 8 +- srsue/src/stack/upper/nas.cc | 357 +++++--- srsue/test/upper/nas_test.cc | 34 +- 15 files changed, 2294 insertions(+), 815 deletions(-) create mode 100644 lib/include/srslte/common/stack_procedure.h create mode 100644 lib/test/common/stack_procedure_test.cc create mode 100644 srsue/hdr/stack/rrc/rrc_procedures.h create mode 100644 srsue/src/stack/rrc/rrc_procedures.cc diff --git a/lib/include/srslte/common/stack_procedure.h b/lib/include/srslte/common/stack_procedure.h new file mode 100644 index 000000000..5e39ccd1f --- /dev/null +++ b/lib/include/srslte/common/stack_procedure.h @@ -0,0 +1,317 @@ +/* + * Copyright 2013-2019 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 +#include +#include +#include + +#ifndef SRSLTE_RESUMABLE_PROCEDURES_H +#define SRSLTE_RESUMABLE_PROCEDURES_H + +namespace srslte { + +enum class proc_state_t { on_going, success, error, inactive }; +enum class proc_outcome_t { repeat, yield, success, error }; + +/************************************************************************************** + * class: proc_impl_t + * Provides an polymorphic interface for resumable procedures. This base can then be used + * by a task dispatch queue via the method "run()". + * Every procedure starts in inactive state, and finishes with success or error. + * With methods: + * - run() - executes a procedure, returning true if the procedure is still running + * or false, if it has completed + * - step() - method overriden by child class that will be called by run(). step() + * executes a procedure "action" based on its current internal state, + * and return a proc_outcome_t variable with possible values: + * - yield - the procedure performed the action but hasn't completed yet. + * - repeat - the same as yield, but explicitly asking that run() should + * recall step() again (probably the procedure state has changed) + * - error - the procedure has finished unsuccessfully + * - success - the procedure has completed successfully + * - stop() - called automatically when a procedure has finished. Useful for actions + * upon procedure completion, like sending back a response. + * - set_proc_state() / is_#() - setter and getters for current procedure state + ************************************************************************************/ +class proc_impl_t +{ +public: + proc_impl_t() : proc_state(proc_state_t::inactive) {} + virtual ~proc_impl_t() = default; + virtual proc_outcome_t step() = 0; + virtual void stop() {} // may be overloaded + + bool run() + { + proc_outcome_t outcome = proc_outcome_t::repeat; + while (is_running() and outcome == proc_outcome_t::repeat) { + outcome = step(); + if (outcome == proc_outcome_t::error) { + set_proc_state(proc_state_t::error); + } else if (outcome == proc_outcome_t::success) { + set_proc_state(proc_state_t::success); + } + } + return is_running(); + } + + void set_proc_state(proc_state_t new_state) + { + proc_state = new_state; + if (proc_state == proc_state_t::error or proc_state == proc_state_t::success) { + stop(); + } + } + bool is_error() const { return proc_state == proc_state_t::error; } + bool is_success() const { return proc_state == proc_state_t::success; } + bool is_running() const { return proc_state == proc_state_t::on_going; } + bool is_complete() const { return is_success() or is_error(); } + bool is_active() const { return proc_state != proc_state_t::inactive; } + +private: + proc_state_t proc_state; +}; + +/************************************************************************************** + * class: proc_t + * Handles the lifetime, of a procedure T that derives from proc_impl_t, including + * its alloc, initialization, and reset back to initial state once the procedure has been + * completed and the user has extracted its results. + * Can only be re-launched when a procedure T becomes inactive. + * It uses a unique_ptr to allow the use of procedures that are forward declared. + * It provides the following methods: + * - run() - calls proc_impl_t::run(). See above. + * - launch() - initializes the procedure T by calling T::init(...). Handles the case + * of failed initialization, and forbids the initialization of procedures + * that are already active. + * - pop() - extracts the result of the procedure if it has finished, and sets + * proc_t back to inactive + * - trigger_event(Event) - used for handling external events. The procedure T will + * have to define a method "trigger_event(Event)" as well, + * specifying how each event type should be handled. + ************************************************************************************/ +template +class proc_t +{ +public: + explicit proc_t() : proc_impl_ptr(new T()) {} + T* get() { return proc_impl_ptr.get(); } + bool is_active() const { return proc_impl_ptr->is_active(); } + bool is_complete() const { return proc_impl_ptr->is_complete(); } + T* release() { return proc_impl_ptr.release(); } + bool run() { return proc_impl_ptr->run(); } + void clear() + { + // Destructs the current object, and calls default ctor (which sets proc back to inactive and ready for another run) + proc_impl_ptr->~T(); + new (proc_impl_ptr.get()) T(); + } + + template + void trigger_event(Event&& e) + { + if (proc_impl_ptr->is_running()) { + proc_outcome_t outcome = proc_impl_ptr->trigger_event(std::forward(e)); + if (outcome == proc_outcome_t::error) { + proc_impl_ptr->set_proc_state(proc_state_t::error); + } else if (outcome == proc_outcome_t::success) { + proc_impl_ptr->set_proc_state(proc_state_t::success); + } + } + } + + T pop() + { + if (not proc_impl_ptr->is_complete()) { + return T(); + } + T ret(std::move(*proc_impl_ptr)); + clear(); + return ret; + } + + template + bool launch(Args&&... args) + { + if (is_active()) { + // if already active + return false; + } + proc_impl_ptr->set_proc_state(proc_state_t::on_going); + proc_outcome_t init_ret = proc_impl_ptr->init(std::forward(args)...); + switch (init_ret) { + case proc_outcome_t::error: + proc_impl_ptr->set_proc_state(proc_state_t::error); // call stop as an error + clear(); + return false; + case proc_outcome_t::success: + proc_impl_ptr->set_proc_state(proc_state_t::success); + break; + case proc_outcome_t::repeat: + run(); // call run right away + break; + case proc_outcome_t::yield: + break; + } + return true; + } + +private: + std::unique_ptr proc_impl_ptr; +}; + +/************************************************************************************** + * class: func_proc_t + * A proc_impl_t used to store lambda functions and other function pointers as a step() + * method, avoiding this way, always having to create a new class per procedure. + ************************************************************************************/ +class func_proc_t : public proc_impl_t +{ +public: + proc_outcome_t init(std::function step_func_) + { + step_func = std::move(step_func_); + return proc_outcome_t::yield; + } + + proc_outcome_t step() final { return step_func(); } + +private: + std::function step_func; +}; + +/************************************************************************************** + * class: func_proc_t + * A helper proc_impl_t whose step()/stop() are no op, but has a trigger_event() that + * signals that the method has finished and store a result of type OutcomeType. + ************************************************************************************/ +template +class query_proc_t : public proc_impl_t +{ +public: + proc_outcome_t init() { return proc_outcome_t::yield; } + proc_outcome_t step() final { return proc_outcome_t::yield; } + + proc_outcome_t trigger_event(const OutcomeType& outcome_) + { + outcome = outcome_; + return proc_outcome_t::success; + } + + const OutcomeType& result() const { return outcome; } + +private: + OutcomeType outcome; +}; + +/************************************************************************************** + * class: callback_list_t + * Stores procedures that derive from proc_impl_t. Its run() method calls sequentially + * all the stored procedures run() method, and removes the procedures if they have + * completed. + * There are different ways to add a procedure to the list: + * - add_proc(...) - adds a proc_t, and once the procedure has completed, takes it + * out of the container without resetting it back to its initial state + * or deleting. This is useful, if the user wants to extract the + * procedure result via proc_t::pop() + * - consume_proc(...) - receives a proc_t as a rvalue, and calls the proc_t + * destructor once the procedure has ended. Useful, for procedures + * for which the user is not interested in the result, or reusing + * - defer_proc(...) - same as add_proc(...), but once the procedure has finished, it + * automatically sets the procedure back to its initial state. + * Useful if the user is not interested in handling the result + * - defer_task(...) - same as consume_proc(...) but takes a function pointer that + * specifies a proc_impl_t step() function + ************************************************************************************/ +class callback_list_t +{ +public: + typedef std::function proc_deleter_t; + typedef std::unique_ptr callback_obj_t; + template + struct recycle_deleter_t { + void operator()(proc_impl_t* p) + { + if (p != nullptr) { + T* Tp = static_cast(p); + Tp->~T(); + new (Tp) T(); + } + } + }; + + template + void add_proc(proc_t& proc) + { + if (proc.is_complete()) { + return; + } + callback_obj_t ptr(proc.get(), [](proc_impl_t* p) { /* do nothing */ }); + callbacks.push_back(std::move(ptr)); + } + + template + void consume_proc(proc_t&& proc) + { + if (proc.is_complete()) { + return; + } + callback_obj_t ptr(proc.release(), std::default_delete()); + callbacks.push_back(std::move(ptr)); + } + + template + void defer_proc(proc_t& proc) + { + if (proc.is_complete()) { + proc.pop(); + return; + } + callback_obj_t ptr(proc.get(), recycle_deleter_t()); + callbacks.push_back(std::move(ptr)); + } + + bool defer_task(std::function step_func) + { + proc_t proc; + if (not proc.launch(std::move(step_func))) { + return false; + } + consume_proc(std::move(proc)); + return true; + } + + void run() + { + // Calls run for all callbacks. Remove the ones that have finished. The proc dtor is called. + callbacks.remove_if([](callback_obj_t& elem) { return not elem->run(); }); + } + + size_t size() const { return callbacks.size(); } + +private: + std::list callbacks; +}; + +} // namespace srslte + +#endif // SRSLTE_RESUMABLE_PROCEDURES_H diff --git a/lib/include/srslte/interfaces/rrc_interface_types.h b/lib/include/srslte/interfaces/rrc_interface_types.h index caaa532a1..2f28f4b8e 100644 --- a/lib/include/srslte/interfaces/rrc_interface_types.h +++ b/lib/include/srslte/interfaces/rrc_interface_types.h @@ -124,6 +124,7 @@ struct plmn_id_t { struct s_tmsi_t { uint8_t mmec = 0; uint32_t m_tmsi = 0; + bool operator==(const s_tmsi_t& other) const { return mmec == other.mmec and m_tmsi == other.m_tmsi; } }; /*************************** diff --git a/lib/include/srslte/interfaces/ue_interfaces.h b/lib/include/srslte/interfaces/ue_interfaces.h index 3e2b8ac81..27d378ef3 100644 --- a/lib/include/srslte/interfaces/ue_interfaces.h +++ b/lib/include/srslte/interfaces/ue_interfaces.h @@ -36,6 +36,7 @@ #include "srslte/common/common.h" #include "srslte/common/interfaces_common.h" #include "srslte/common/security.h" +#include "srslte/common/stack_procedure.h" #include "srslte/phy/channel/channel.h" #include "srslte/phy/rf/rf.h" @@ -118,43 +119,6 @@ public: virtual void write_pdu_mch(uint32_t lcid, srslte::unique_byte_buffer_t pdu) = 0; }; -// NAS interface for RRC -class nas_interface_rrc -{ -public: - typedef enum { - BARRING_NONE = 0, - BARRING_MO_DATA, - BARRING_MO_SIGNALLING, - BARRING_MT, - BARRING_ALL - } barring_t; - virtual void leave_connected() = 0; - virtual void set_barring(barring_t barring) = 0; - virtual void paging(srslte::s_tmsi_t* ue_identity) = 0; - virtual bool is_attached() = 0; - virtual void write_pdu(uint32_t lcid, srslte::unique_byte_buffer_t pdu) = 0; - virtual uint32_t get_k_enb_count() = 0; - virtual bool get_k_asme(uint8_t* k_asme_, uint32_t n) = 0; - virtual uint32_t get_ipv4_addr() = 0; - virtual bool get_ipv6_addr(uint8_t* ipv6_addr) = 0; -}; - -// NAS interface for UE -class nas_interface_ue -{ -public: - virtual bool attach_request() = 0; - virtual bool detach_request() = 0; -}; - -// NAS interface for UE -class nas_interface_gw -{ -public: - virtual bool attach_request() = 0; -}; - // RRC interface for MAC class rrc_interface_mac_common { @@ -189,18 +153,19 @@ public: const static int MAX_FOUND_PLMNS = 16; - virtual void write_sdu(srslte::unique_byte_buffer_t sdu) = 0; - virtual uint16_t get_mcc() = 0; - virtual uint16_t get_mnc() = 0; - virtual void enable_capabilities() = 0; - virtual int plmn_search(found_plmn_t found_plmns[MAX_FOUND_PLMNS]) = 0; - virtual void plmn_select(srslte::plmn_id_t plmn_id) = 0; + virtual void write_sdu(srslte::unique_byte_buffer_t sdu) = 0; + virtual uint16_t get_mcc() = 0; + virtual uint16_t get_mnc() = 0; + virtual void enable_capabilities() = 0; + virtual bool plmn_search() = 0; + virtual void plmn_select(srslte::plmn_id_t plmn_id) = 0; virtual bool connection_request(srslte::establishment_cause_t cause, - srslte::unique_byte_buffer_t dedicatedInfoNAS) = 0; - virtual void set_ue_identity(srslte::s_tmsi_t s_tmsi) = 0; - virtual bool is_connected() = 0; - virtual std::string get_rb_name(uint32_t lcid) = 0; - virtual uint32_t get_lcid_for_eps_bearer(const uint32_t& eps_bearer_id) = 0; + srslte::unique_byte_buffer_t dedicatedInfoNAS) = 0; + virtual void set_ue_identity(srslte::s_tmsi_t s_tmsi) = 0; + virtual bool is_connected() = 0; + virtual void paging_completed(bool outcome) = 0; + virtual std::string get_rb_name(uint32_t lcid) = 0; + virtual uint32_t get_lcid_for_eps_bearer(const uint32_t& eps_bearer_id) = 0; }; // RRC interface for PDCP @@ -224,6 +189,34 @@ public: virtual void write_pdu(uint32_t lcid, srslte::unique_byte_buffer_t pdu) = 0; }; +// NAS interface for RRC +class nas_interface_rrc +{ +public: + typedef enum { BARRING_NONE = 0, BARRING_MO_DATA, BARRING_MO_SIGNALLING, BARRING_MT, BARRING_ALL } barring_t; + virtual void leave_connected() = 0; + virtual void set_barring(barring_t barring) = 0; + virtual void paging(srslte::s_tmsi_t* ue_identity) = 0; + virtual bool is_attached() = 0; + virtual void write_pdu(uint32_t lcid, srslte::unique_byte_buffer_t pdu) = 0; + virtual uint32_t get_k_enb_count() = 0; + virtual bool get_k_asme(uint8_t* k_asme_, uint32_t n) = 0; + virtual uint32_t get_ipv4_addr() = 0; + virtual bool get_ipv6_addr(uint8_t* ipv6_addr) = 0; + virtual void plmn_search_completed(rrc_interface_nas::found_plmn_t found_plmns[rrc_interface_nas::MAX_FOUND_PLMNS], + int nof_plmns) = 0; + virtual bool connection_request_completed(bool outcome) = 0; + virtual void run_tti(uint32_t tti) = 0; +}; + +// NAS interface for UE +class nas_interface_ue +{ +public: + virtual void start_attach_request(srslte::proc_state_t* proc_result) = 0; + virtual bool detach_request() = 0; +}; + // PDCP interface for RRC class pdcp_interface_rrc { diff --git a/lib/include/srslte/upper/pdcp.h b/lib/include/srslte/upper/pdcp.h index 1c6ab546c..3950805d4 100644 --- a/lib/include/srslte/upper/pdcp.h +++ b/lib/include/srslte/upper/pdcp.h @@ -29,7 +29,7 @@ namespace srslte { -class pdcp : public srsue::pdcp_interface_gw, public srsue::pdcp_interface_rlc, public srsue::pdcp_interface_rrc +class pdcp : public srsue::pdcp_interface_rlc, public srsue::pdcp_interface_rrc { public: pdcp(log* log_); diff --git a/lib/test/common/CMakeLists.txt b/lib/test/common/CMakeLists.txt index c358b0993..2ceccc687 100644 --- a/lib/test/common/CMakeLists.txt +++ b/lib/test/common/CMakeLists.txt @@ -55,4 +55,6 @@ add_executable(bcd_helpers_test bcd_helpers_test.cc) add_executable(pdu_test pdu_test.cc) target_link_libraries(pdu_test srslte_phy srslte_common ${CMAKE_THREAD_LIBS_INIT}) -add_test(pdu_test pdu_test) \ No newline at end of file +add_test(pdu_test pdu_test) + +add_executable(stack_procedure_test stack_procedure_test.cc) diff --git a/lib/test/common/stack_procedure_test.cc b/lib/test/common/stack_procedure_test.cc new file mode 100644 index 000000000..15b0adc55 --- /dev/null +++ b/lib/test/common/stack_procedure_test.cc @@ -0,0 +1,312 @@ +/* + * Copyright 2013-2019 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/stack_procedure.h" +#include + +#define TESTASSERT(cond) \ + { \ + if (!(cond)) { \ + std::cout << "[" << __FUNCTION__ << "][Line " << __LINE__ << "]: FAIL at " << (#cond) << std::endl; \ + return -1; \ + } \ + } + +using srslte::proc_outcome_t; + +enum class obj_state_t { default_ctor, move_ctor, copy_ctor, from_move_ctor, from_move_assign }; + +const char* to_string(obj_state_t o) +{ + switch (o) { + case obj_state_t::default_ctor: + return "default_ctor"; + case obj_state_t::move_ctor: + return "move_ctor"; + case obj_state_t::copy_ctor: + return "copy_ctor"; + case obj_state_t::from_move_ctor: + return "from_move_ctor"; + case obj_state_t::from_move_assign: + return "from_move_assign"; + } + return ""; +} + +class TestObj +{ +public: + TestObj() = default; + TestObj(const TestObj& other) + { + printf("TestObj copy ctor called: {%s,%d} <- {%s,%d}!!!\n", to_string(state), id, to_string(other.state), other.id); + id = other.id; + state = obj_state_t::copy_ctor; + copy_counter++; + } + TestObj(TestObj&& other) noexcept + { + printf("TestObj move ctor called: {%s,%d} <- {%s,%d}!!!\n", to_string(state), id, to_string(other.state), other.id); + id = other.id; + state = obj_state_t::move_ctor; + other.state = obj_state_t::from_move_ctor; + move_counter++; + } + ~TestObj() + { + dtor_counter++; + printf("TestObj {%s,%d} dtor called!!!\n", to_string(state), id); + } + TestObj& operator=(const TestObj& other) + { + printf("TestObj copy operator called: {%s,%d} <- {%s,%d}!!!\n", + to_string(state), + id, + to_string(other.state), + other.id); + id = other.id; + state = other.state; + copy_counter++; + return *this; + } + TestObj& operator=(TestObj&& other) noexcept + { + printf("TestObj move operator called: {%s,%d} <- {%s,%d}!!!\n", + to_string(state), + id, + to_string(other.state), + other.id); + if (&other != this) { + id = other.id; + state = other.state; + other.state = obj_state_t::from_move_assign; + move_counter++; + } + return *this; + } + + obj_state_t state = obj_state_t::default_ctor; + int id = 0; + static int copy_counter; + static int move_counter; + static int dtor_counter; +}; + +int TestObj::copy_counter = 0; +int TestObj::move_counter = 0; +int TestObj::dtor_counter = 0; + +void new_test() +{ + TestObj::copy_counter = 0; + TestObj::move_counter = 0; + TestObj::dtor_counter = 0; +} + +class custom_proc : public srslte::proc_impl_t +{ +public: + proc_outcome_t init(int a_) + { + if (a_ < 0) { + printf("Failed to initiate custom_proc\n"); + return proc_outcome_t::error; + } + obj.id = a_; + return proc_outcome_t::yield; + } + proc_outcome_t step() final + { + if (counter++ > 5) { + return proc_outcome_t::success; + } + return proc_outcome_t::yield; + } + void stop() final { printf("TestObj %d stop() was called\n", obj.id); } + const char* name() const { return "custom proc"; } + + TestObj obj; + +private: + int counter = 0; +}; + +int test_local_1() +{ + new_test(); + printf("\n--- Test %s ---\n", __func__); + srslte::proc_t proc; + TESTASSERT(not proc.is_active()); + + proc.launch(1); + TESTASSERT(proc.is_active()); + TESTASSERT(not proc.is_complete()); + + while (proc.run()) { + } + + TESTASSERT(proc.is_active()); + TESTASSERT(proc.is_complete()); + + printf("pop being called\n"); + custom_proc procobj = proc.pop(); + TESTASSERT(procobj.obj.id == 1); + TESTASSERT(procobj.is_success()); + TESTASSERT(not proc.is_active()); + TESTASSERT(proc.get()->obj.id == 0); // Proc is ready to be reused + + printf("EXIT\n"); + TESTASSERT(TestObj::copy_counter == 0); + TESTASSERT(TestObj::move_counter == 2); // pop() makes a swap which causes 2 moves + TESTASSERT(TestObj::dtor_counter == 2); // 2 dtors inside pop() (handler and popped obj not yet destructed) + return 0; +} + +int test_callback_1() +{ + new_test(); + printf("\n--- Test %s ---\n", __func__); + srslte::callback_list_t callbacks; + srslte::proc_t proc; + TESTASSERT(not proc.is_active()); + + proc.launch(2); + callbacks.add_proc(proc); // We have to call pop() explicitly to take the result + TESTASSERT(callbacks.size() == 1); + + while (callbacks.size() > 0) { + TESTASSERT(proc.is_active()); + TESTASSERT(not proc.is_complete()); + callbacks.run(); + } + TESTASSERT(proc.is_active()); + TESTASSERT(proc.is_complete()); + + printf("pop being called\n"); + custom_proc procobj = proc.pop(); + TESTASSERT(procobj.is_success()); + TESTASSERT(procobj.obj.id == 2); + TESTASSERT(not proc.is_active()); + + TESTASSERT(proc.get()->obj.id == 0); // Proc is ready to be reused + printf("EXIT\n"); + TESTASSERT(TestObj::copy_counter == 0); + TESTASSERT(TestObj::move_counter == 2); // pop makes two moves + TESTASSERT(TestObj::dtor_counter == 2); // handler not yet destructed + return 0; +} + +int test_callback_2() +{ + new_test(); + printf("\n--- Test %s ---\n", __func__); + srslte::callback_list_t callbacks; + srslte::proc_t proc; + TESTASSERT(not proc.is_active()); + + proc.launch(3); + TESTASSERT(proc.is_active()); + TESTASSERT(not proc.is_complete()); + callbacks.consume_proc(std::move(proc)); + TESTASSERT(callbacks.size() == 1); + + while (callbacks.size() > 0) { + callbacks.run(); + } + // since the proc was consumed, it is erased without the need for pop() + + printf("EXIT\n"); + TESTASSERT(TestObj::copy_counter == 0); + TESTASSERT(TestObj::move_counter == 0); // no pop() + TESTASSERT(TestObj::dtor_counter == 1); // handler not yet destructed + return 0; +} + +int test_callback_3() +{ + new_test(); + printf("\n--- Test %s ---\n", __func__); + srslte::callback_list_t callbacks; + srslte::proc_t proc; + TESTASSERT(not proc.is_active()); + + proc.launch(4); + TESTASSERT(proc.is_active()); + TESTASSERT(not proc.is_complete()); + callbacks.defer_proc(proc); // we still have access to proc, but we do not need to call pop() + TESTASSERT(callbacks.size() == 1); + TESTASSERT(proc.is_active()); + TESTASSERT(not proc.is_complete()); + + while (callbacks.size() > 0) { + TESTASSERT(proc.is_active()); + TESTASSERT(not proc.is_complete()); + callbacks.run(); + } + TESTASSERT(not proc.is_active()); + TESTASSERT(not proc.is_complete()); + TESTASSERT(proc.get()->obj.id == 0); // Proc is ready to be reused + + printf("EXIT\n"); + TESTASSERT(TestObj::copy_counter == 0); + TESTASSERT(TestObj::move_counter == 0); + TESTASSERT(TestObj::dtor_counter == 1); // handler not yet destructed + return 0; +} + +int test_callback_4() +{ + new_test(); + printf("\n--- Test %s ---\n", __func__); + srslte::callback_list_t callbacks; + int* counter = new int(5); + + { + callbacks.defer_task([counter]() { + printf("current counter=%d\n", *counter); + if (--(*counter) == 0) { + return proc_outcome_t::success; + } + return proc_outcome_t::yield; + }); + } + + while (callbacks.size() > 0) { + callbacks.run(); + } + + // printf("counter=%d\n", counter); + TESTASSERT(*counter == 0); + delete counter; + + return 0; +} + +int main() +{ + TESTASSERT(test_local_1() == 0); + TESTASSERT(test_callback_1() == 0); + TESTASSERT(test_callback_2() == 0); + TESTASSERT(test_callback_3() == 0); + TESTASSERT(test_callback_4() == 0); + + return 0; +} diff --git a/srsue/hdr/stack/rrc/rrc.h b/srsue/hdr/stack/rrc/rrc.h index 6b7a56f8b..42311791f 100644 --- a/srsue/hdr/stack/rrc/rrc.h +++ b/srsue/hdr/stack/rrc/rrc.h @@ -33,6 +33,7 @@ #include "srslte/common/common.h" #include "srslte/common/log.h" #include "srslte/common/security.h" +#include "srslte/common/stack_procedure.h" #include "srslte/common/threads.h" #include "srslte/interfaces/ue_interfaces.h" @@ -282,8 +283,7 @@ class rrc : public rrc_interface_nas, public rrc_interface_mac, public rrc_interface_pdcp, public rrc_interface_rlc, - public srslte::timer_callback, - public thread + public srslte::timer_callback { public: rrc(srslte::log* rrc_log_); @@ -319,10 +319,11 @@ public: void enable_capabilities(); uint16_t get_mcc(); uint16_t get_mnc(); - int plmn_search(found_plmn_t found_plmns[MAX_FOUND_PLMNS]); + bool plmn_search() final; void plmn_select(srslte::plmn_id_t plmn_id); bool connection_request(srslte::establishment_cause_t cause, srslte::unique_byte_buffer_t dedicated_info_nas); void set_ue_identity(srslte::s_tmsi_t s_tmsi); + void paging_completed(bool outcome) final; // PHY interface void in_sync(); @@ -349,14 +350,13 @@ public: private: typedef struct { - enum { PDU, PCCH, STOP, MBMS_START } command; + enum { PDU, PCCH, PDU_MCH, RLF, PDU_BCCH_DLSCH, STOP } command; srslte::unique_byte_buffer_t pdu; uint16_t lcid; } cmd_msg_t; bool running = false; srslte::block_queue cmd_q; - void run_thread(); void process_pcch(srslte::unique_byte_buffer_t pdu); @@ -377,8 +377,6 @@ private: srslte::bit_buffer_t bit_buf; - pthread_mutex_t mutex; - rrc_state_t state, last_state = RRC_STATE_IDLE; uint8_t transaction_id = 0; srslte::s_tmsi_t ue_identity; @@ -458,23 +456,12 @@ private: std::vector::iterator delete_neighbour(std::vector::iterator it); void delete_neighbour(uint32_t cell_idx); - bool configure_serving_cell(); - - bool si_acquire(uint32_t index); - uint32_t sib_start_tti(uint32_t tti, uint32_t period, uint32_t offset, uint32_t sf); - const static int SIB_SEARCH_TIMEOUT_MS = 1000; - - bool initiated = false; - bool ho_start = false; - bool go_idle = false; + bool initiated = false; asn1::rrc::reest_cause_e m_reest_cause = {}; uint16_t m_reest_rnti = 0; - bool reestablishment_started = false; + bool reestablishment_started = false; bool reestablishment_successful = false; - uint32_t rlc_flush_counter = 0; - uint32_t rlc_flush_timeout = 0; - // Measurements sub-class class rrc_meas { public: @@ -602,20 +589,38 @@ private: float get_srxlev(float Qrxlevmeas); float get_squal(float Qqualmeas); - typedef enum { - CHANGED_CELL = 0, - SAME_CELL = 1, - NO_CELL = 2 - } cs_ret_t; - - cs_ret_t cell_selection(); - bool cell_selection_criteria(float rsrp, float rsrq = 0); - void cell_reselection(float rsrp, float rsrq); - - phy_interface_rrc_lte::cell_search_ret_t cell_search(); - - srslte::plmn_id_t selected_plmn_id = {}; - bool plmn_is_selected = false; + /******************** + * RRC Procedures + *******************/ + + enum class cs_result_t { changed_cell, same_cell, no_cell }; + + // RRC procedures (fwd declared) + class cell_search_proc; + class si_acquire_proc; + class serving_cell_config_proc; + class cell_selection_proc; + class connection_request_proc; + class plmn_search_proc; + class process_pcch_proc; + class go_idle_proc; + srslte::proc_t cell_searcher; + srslte::proc_t si_acquirer; + srslte::proc_t serv_cell_cfg; + srslte::proc_t cell_selector; + srslte::proc_t idle_setter; + srslte::proc_t pcch_processor; + srslte::proc_t conn_req_proc; + srslte::proc_t plmn_searcher; + + srslte::callback_list_t callback_list; + + bool cell_selection_criteria(float rsrp, float rsrq = 0); + void cell_reselection(float rsrp, float rsrq); + + std::vector ue_required_sibs; + srslte::plmn_id_t selected_plmn_id = {}; + bool plmn_is_selected = false; bool security_is_activated = false; @@ -623,7 +628,7 @@ private: void max_retx_attempted(); // Senders - void send_con_request(asn1::rrc::establishment_cause_e cause); + void send_con_request(srslte::establishment_cause_t cause); void send_con_restablish_request(); void send_con_restablish_complete(); void send_con_setup_complete(srslte::unique_byte_buffer_t nas_msg); @@ -636,6 +641,8 @@ private: void parse_dl_ccch(srslte::unique_byte_buffer_t pdu); void parse_dl_dcch(uint32_t lcid, srslte::unique_byte_buffer_t pdu); void parse_dl_info_transfer(uint32_t lcid, srslte::unique_byte_buffer_t pdu); + void parse_pdu_bcch_dlsch(srslte::unique_byte_buffer_t pdu); + void parse_pdu_mch(uint32_t lcid, srslte::unique_byte_buffer_t pdu); // Helpers bool con_reconfig(asn1::rrc::rrc_conn_recfg_s* reconfig); @@ -643,12 +650,15 @@ private: bool con_reconfig_ho(asn1::rrc::rrc_conn_recfg_s* reconfig); bool ho_prepare(); void ho_failed(); + void start_ho(); + void start_go_idle(); void rrc_connection_release(); void radio_link_failure(); void leave_connected(); void stop_timers(); void init_con_restablish_request(asn1::rrc::reest_cause_e cause); void proc_con_restablish_request(); + void start_cell_reselection(); void log_rr_config_common(); void log_phy_config_dedicated(); diff --git a/srsue/hdr/stack/rrc/rrc_procedures.h b/srsue/hdr/stack/rrc/rrc_procedures.h new file mode 100644 index 000000000..0e898d375 --- /dev/null +++ b/srsue/hdr/stack/rrc/rrc_procedures.h @@ -0,0 +1,198 @@ +/* + * Copyright 2013-2019 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/log.h" +#include "srsue/hdr/stack/rrc/rrc.h" +#include +#include + +#ifndef SRSLTE_RRC_PROCEDURES_H +#define SRSLTE_RRC_PROCEDURES_H + +namespace srsue { + +class rrc::cell_search_proc : public srslte::proc_impl_t +{ +public: + enum class state_t { phy_cell_search, si_acquire }; + + srslte::proc_outcome_t init(rrc* parent_); + srslte::proc_outcome_t step() final; + + phy_interface_rrc_lte::cell_search_ret_t get_cs_ret() { return cs_ret; } + static const char* name() { return "Cell Search"; } + +private: + srslte::proc_outcome_t handle_cell_found(const phy_interface_rrc_lte::phy_cell_t& new_cell); + + // conts + rrc* rrc_ptr; + srslte::log* log_h; + + // state vars + phy_interface_rrc_lte::cell_search_ret_t cs_ret; + state_t state; +}; + +class rrc::si_acquire_proc : public srslte::proc_impl_t +{ +public: + const static int SIB_SEARCH_TIMEOUT_MS = 1000; + + srslte::proc_outcome_t init(rrc* parent_, uint32_t sib_index_); + srslte::proc_outcome_t step() final; + static const char* name() { return "SI Acquire"; } + +private: + static uint32_t sib_start_tti(uint32_t tti, uint32_t period, uint32_t offset, uint32_t sf); + + // conts + rrc* rrc_ptr; + srslte::log* log_h; + + // state + uint32_t period, sched_index; + uint32_t start_tti = 0; + uint32_t sib_index = 0; + uint32_t last_win_start = 0; +}; + +class rrc::serving_cell_config_proc : public srslte::proc_impl_t +{ +public: + srslte::proc_outcome_t init(rrc* parent_, const std::vector& required_sibs_); + srslte::proc_outcome_t step() final; + static const char* name() { return "Serving Cell Configuration"; } + +private: + // consts + std::vector required_sibs; + rrc* rrc_ptr; + srslte::log* log_h; + + // state variables + enum class search_state_t { next_sib, si_acquire } search_state; + uint32_t req_idx = 0; +}; + +class rrc::cell_selection_proc : public srslte::proc_impl_t +{ +public: + srslte::proc_outcome_t init(rrc* parent_); + srslte::proc_outcome_t step() final; + cs_result_t get_cs_result() { return cs_result; } + static const char* name() { return "Cell Selection"; } + +private: + srslte::proc_outcome_t step_cell_selection(); + srslte::proc_outcome_t step_cell_search(); + srslte::proc_outcome_t step_cell_config(); + + // consts + rrc* rrc_ptr; + srslte::log* log_h; + + // state variables + enum class search_state_t { cell_selection, cell_config, cell_search }; + cs_result_t cs_result; + search_state_t state; + uint32_t neigh_index; +}; + +class rrc::plmn_search_proc : public srslte::proc_impl_t +{ +public: + srslte::proc_outcome_t init(rrc* parent_); + srslte::proc_outcome_t step() final; + void stop() final; + static const char* name() { return "PLMN Search"; } + +private: + // consts + rrc* rrc_ptr; + srslte::log* log_h; + + // state variables + found_plmn_t found_plmns[MAX_FOUND_PLMNS]; + int nof_plmns; +}; + +class rrc::connection_request_proc : public srslte::proc_impl_t +{ +public: + srslte::proc_outcome_t + init(rrc* parent_, srslte::establishment_cause_t cause_, srslte::unique_byte_buffer_t dedicated_info_nas_); + srslte::proc_outcome_t step() final; + void stop() final; + static const char* name() { return "Connection Request"; } + +private: + // args + rrc* rrc_ptr; + srslte::log* log_h; + srslte::establishment_cause_t cause; + srslte::unique_byte_buffer_t dedicated_info_nas; + + // state variables + enum class state_t { cell_selection, config_serving_cell, wait_t300 } state; + cs_result_t cs_ret; +}; + +class rrc::process_pcch_proc : public srslte::proc_impl_t +{ +public: + struct paging_complete { + bool outcome; + }; + + srslte::proc_outcome_t init(rrc* parent_, const asn1::rrc::paging_s& paging_); + srslte::proc_outcome_t step() final; + srslte::proc_outcome_t trigger_event(paging_complete e); + static const char* name() { return "Process PCCH"; } + +private: + // args + rrc* rrc_ptr; + srslte::log* log_h; + asn1::rrc::paging_s paging; + + // vars + uint32_t paging_idx = 0; + enum class state_t { next_record, nas_paging, serv_cell_cfg } state; +}; + +class rrc::go_idle_proc : public srslte::proc_impl_t +{ +public: + srslte::proc_outcome_t init(rrc* rrc_); + srslte::proc_outcome_t step() final; + static const char* name() { return "Go Idle"; } + +private: + rrc* rrc_ptr; + static const uint32_t rlc_flush_timeout = 2000; + + uint32_t rlc_flush_counter; +}; + +} // namespace srsue + +#endif // SRSLTE_RRC_PROCEDURES_H diff --git a/srsue/hdr/stack/upper/nas.h b/srsue/hdr/stack/upper/nas.h index e596319f2..2545e4d6f 100644 --- a/srsue/hdr/stack/upper/nas.h +++ b/srsue/hdr/stack/upper/nas.h @@ -28,6 +28,7 @@ #include "srslte/common/log.h" #include "srslte/common/nas_pcap.h" #include "srslte/common/security.h" +#include "srslte/common/stack_procedure.h" #include "srslte/interfaces/ue_interfaces.h" #include "srsue/hdr/stack/upper/nas_common.h" #include "srsue/hdr/stack/upper/nas_metrics.h" @@ -36,15 +37,13 @@ using srslte::byte_buffer_t; namespace srsue { -class nas - : public nas_interface_rrc, - public nas_interface_ue, - public nas_interface_gw +class nas : public nas_interface_rrc, public nas_interface_ue { public: nas(srslte::log* log_); void init(usim_interface_nas* usim_, rrc_interface_nas* rrc_, gw_interface_nas* gw_, const nas_args_t& args_); void stop(); + void run_tti(uint32_t tti) final; void get_metrics(nas_metrics_t* m); emm_state_t get_state(); @@ -61,8 +60,14 @@ public: bool get_ipv6_addr(uint8_t* ipv6_addr); // UE interface - bool attach_request(); - bool detach_request(); + void start_attach_request(srslte::proc_state_t* result) final; + bool detach_request() final; + + void plmn_search_completed(rrc_interface_nas::found_plmn_t found_plmns[rrc_interface_nas::MAX_FOUND_PLMNS], + int nof_plmns) final; + bool start_connection_request(srslte::establishment_cause_t establish_cause, + srslte::unique_byte_buffer_t ded_info_nas); + bool connection_request_completed(bool outcome) final; // PCAP void start_pcap(srslte::nas_pcap *pcap_); @@ -135,8 +140,6 @@ private: bool running = false; - bool rrc_connect(); - void integrity_generate(uint8_t *key_128, uint32_t count, uint8_t direction, @@ -240,6 +243,50 @@ private: } return list; } + + class rrc_connect_proc : public srslte::proc_impl_t + { + public: + struct connection_request_completed_t { + bool outcome; + }; + + srslte::proc_outcome_t init(nas* nas_ptr_); + srslte::proc_outcome_t step() final; + static const char* name() { return "RRC Connect"; } + + private: + nas* nas_ptr; + enum class state_t { conn_req, wait_attach } state; + uint32_t wait_timeout; + }; + class plmn_search_proc : public srslte::proc_impl_t + { + public: + struct plmn_search_complete_t { + rrc_interface_nas::found_plmn_t found_plmns[rrc_interface_nas::MAX_FOUND_PLMNS]; + int nof_plmns; + plmn_search_complete_t(rrc_interface_nas::found_plmn_t* plmns_, int nof_plmns_) : nof_plmns(nof_plmns_) + { + if (nof_plmns > 0) { + std::copy(&plmns_[0], &plmns_[nof_plmns], found_plmns); + } + } + }; + + srslte::proc_outcome_t init(nas* nas_ptr_); + srslte::proc_outcome_t step() final; + srslte::proc_outcome_t trigger_event(const plmn_search_complete_t& t); + static const char* name() { return "PLMN Search"; } + + private: + nas* nas_ptr; + enum class state_t { plmn_search, rrc_connect } state; + }; + srslte::callback_list_t callbacks; + srslte::proc_t plmn_searcher; + srslte::proc_t rrc_connector; + srslte::proc_t > conn_req_proc; }; } // namespace srsue diff --git a/srsue/src/stack/rrc/CMakeLists.txt b/srsue/src/stack/rrc/CMakeLists.txt index 91fc5224f..e8d85f2db 100644 --- a/srsue/src/stack/rrc/CMakeLists.txt +++ b/srsue/src/stack/rrc/CMakeLists.txt @@ -18,9 +18,8 @@ # and at http://www.gnu.org/licenses/. # -set(SOURCES rrc.cc) +set(SOURCES rrc.cc rrc_procedures.cc) add_library(srsue_rrc STATIC ${SOURCES}) install(TARGETS srsue_rrc DESTINATION ${LIBRARY_DIR}) - diff --git a/srsue/src/stack/rrc/rrc.cc b/srsue/src/stack/rrc/rrc.cc index 5b900e0e4..15195efd3 100644 --- a/srsue/src/stack/rrc/rrc.cc +++ b/srsue/src/stack/rrc/rrc.cc @@ -23,6 +23,7 @@ #include "srslte/asn1/rrc_asn1.h" #include "srslte/common/bcd_helpers.h" #include "srslte/common/security.h" +#include "srsue/hdr/stack/rrc/rrc_procedures.h" #include #include #include // for printing uint64_t @@ -46,14 +47,11 @@ const static uint32_t required_sibs[NOF_REQUIRED_SIBS] = {0,1,2,12}; // SIB1, SI Base functions *******************************************************************************/ -rrc::rrc(srslte::log* log_) : - rrc_log(log_), +rrc::rrc(srslte::log* rrc_log_) : state(RRC_STATE_IDLE), last_state(RRC_STATE_CONNECTED), drb_up(false), - rlc_flush_timeout(2000), - rlc_flush_counter(0), - thread("RRC") + rrc_log(rrc_log_) { n310_cnt = 0; n311_cnt = 0; @@ -61,7 +59,6 @@ rrc::rrc(srslte::log* log_) : neighbour_cells.reserve(NOF_NEIGHBOUR_CELLS); initiated = false; running = false; - go_idle = false; m_reest_cause = asn1::rrc::reest_cause_e::nulltype; m_reest_rnti = 0; reestablishment_successful = false; @@ -142,7 +139,6 @@ void rrc::init(phy_interface_rrc_lte* phy_, security_is_activated = false; - pthread_mutex_init(&mutex, NULL); t300 = mac_timers->timer_get_unique_id(); t301 = mac_timers->timer_get_unique_id(); @@ -160,8 +156,6 @@ void rrc::init(phy_interface_rrc_lte* phy_, cell_clean_cnt = 0; - ho_start = false; - pending_mob_reconf = false; // Set default values for all layers @@ -178,8 +172,10 @@ void rrc::init(phy_interface_rrc_lte* phy_, // set seed (used in CHAP auth and attach) srand(tv.tv_usec); + // initiate unique procedures + ue_required_sibs.assign(&required_sibs[0], &required_sibs[NOF_REQUIRED_SIBS]); + running = true; - start(); initiated = true; } @@ -189,7 +185,6 @@ void rrc::stop() { cmd_msg_t msg; msg.command = cmd_msg_t::STOP; cmd_q.push(std::move(msg)); - wait_thread_finish(); } void rrc::get_metrics(rrc_metrics_t& m) @@ -201,30 +196,8 @@ bool rrc::is_connected() { return (RRC_STATE_CONNECTED == state); } -/* - * Low priority thread to run functions that can not be executed from main thread - */ -void rrc::run_thread() { - while(running) { - cmd_msg_t msg = cmd_q.wait_pop(); - switch(msg.command) { - case cmd_msg_t::PDU: - process_pdu(msg.lcid, std::move(msg.pdu)); - break; - case cmd_msg_t::PCCH: - process_pcch(std::move(msg.pdu)); - break; - case cmd_msg_t::MBMS_START: - if (args.mbms_service_id >= 0) { - rrc_log->info("Attempting to auto-start MBMS service %d\n", - args.mbms_service_id); - mbms_service_start(args.mbms_service_id, args.mbms_service_port); - } - break; - case cmd_msg_t::STOP: - return; - } - } +bool rrc::have_drb() { + return drb_up; } /* @@ -234,7 +207,6 @@ void rrc::run_thread() { */ void rrc::run_tti(uint32_t tti) { - if (!initiated) { return; } @@ -244,90 +216,75 @@ void rrc::run_tti(uint32_t tti) simulate_rlf = false; } - /* We can not block in this thread because it is called from - * the MAC TTI timer and needs to return immediatly to perform other - * tasks. Therefore in this function we use trylock() instead of lock() and - * skip function if currently locked, since none of the functions here is urgent - */ - if (!pthread_mutex_trylock(&mutex)) { - - // Process pending PHY measurements in IDLE/CONNECTED - process_phy_meas(); - - // Log state changes - if (state != last_state) { - rrc_log->debug("State %s\n", rrc_state_text[state]); - last_state = state; - } - - // Run state machine - switch (state) { - case RRC_STATE_IDLE: - - /* CAUTION: The execution of cell_search() and cell_selection() take more than 1 ms - * and will slow down MAC TTI ticks. This has no major effect at the moment because - * the UE is in IDLE but we could consider splitting MAC and RRC threads to avoid this - */ - - // If attached but not camping on the cell, perform cell reselection - if (nas->is_attached()) { - rrc_log->debug("Running cell selection and reselection in IDLE\n"); - switch(cell_selection()) { - case rrc::CHANGED_CELL: - // New cell has been selected, start receiving PCCH - mac->pcch_start_rx(); - break; - case rrc::NO_CELL: - rrc_log->warning("Could not find any cell to camp on\n"); - break; - case rrc::SAME_CELL: - if (!phy->cell_is_camping()) { - rrc_log->warning("Did not reselect cell but serving cell is out-of-sync.\n"); - serving_cell->in_sync = false; - } - break; - } - } - break; - case RRC_STATE_CONNECTED: - if (ho_start) { - ho_start = false; - if (!ho_prepare()) { - con_reconfig_failed(); - } - } + // Process pending PHY measurements in IDLE/CONNECTED + process_phy_meas(); - // Performing reestablishment cell selection - if (m_reest_cause != asn1::rrc::reest_cause_e::nulltype) { - proc_con_restablish_request(); - } + // Process on-going callbacks, and clear finished callbacks + callback_list.run(); - measurements.run_tti(tti); - if (go_idle) { - // wait for max. 2s for RLC on SRB1 to be flushed - if (not rlc->has_data(RB_ID_SRB1) || ++rlc_flush_counter > rlc_flush_timeout) { - go_idle = false; - leave_connected(); - } else { - rrc_log->debug("Postponing transition to RRC IDLE (%d ms < %d ms)\n", rlc_flush_counter, rlc_flush_timeout); - } - } - break; - default:break; - } + // Log state changes + if (state != last_state) { + rrc_log->debug("State %s\n", rrc_state_text[state]); + last_state = state; + } - // Clean old neighbours - cell_clean_cnt++; - if (cell_clean_cnt == 1000) { - clean_neighbours(); - cell_clean_cnt = 0; - } - pthread_mutex_unlock(&mutex); + // Run state machine + switch (state) { + case RRC_STATE_IDLE: - // allow other threads to ackquire mutex. - usleep(100); + /* CAUTION: The execution of cell_search() and cell_selection() take more than 1 ms + * and will slow down MAC TTI ticks. This has no major effect at the moment because + * the UE is in IDLE but we could consider splitting MAC and RRC threads to avoid this + */ + + // If attached but not camping on the cell, perform cell reselection + if (nas->is_attached()) { + start_cell_reselection(); + } + break; + case RRC_STATE_CONNECTED: + // Performing reestablishment cell selection + if (m_reest_cause != asn1::rrc::reest_cause_e::nulltype) { + proc_con_restablish_request(); + } + measurements.run_tti(tti); + break; + default: + break; + } + + // Handle Received Messages + if (running) { + cmd_msg_t msg; + if (cmd_q.try_pop(&msg)) { + switch (msg.command) { + case cmd_msg_t::PDU: + process_pdu(msg.lcid, std::move(msg.pdu)); + break; + case cmd_msg_t::PCCH: + process_pcch(std::move(msg.pdu)); + break; + case cmd_msg_t::PDU_MCH: + parse_pdu_mch(msg.lcid, std::move(msg.pdu)); + break; + case cmd_msg_t::RLF: + radio_link_failure(); + break; + case cmd_msg_t::PDU_BCCH_DLSCH: + parse_pdu_bcch_dlsch(std::move(msg.pdu)); + break; + case cmd_msg_t::STOP: + return; + } + } + } - } // Skip TTI if mutex is locked + // Clean old neighbours + cell_clean_cnt++; + if (cell_clean_cnt == 1000) { + clean_neighbours(); + cell_clean_cnt = 0; + } } @@ -363,45 +320,14 @@ uint16_t rrc::get_mnc() { * * This function is thread-safe with connection_request() */ -int rrc::plmn_search(found_plmn_t found_plmns[MAX_FOUND_PLMNS]) -{ - // Mutex with connect - pthread_mutex_lock(&mutex); - - rrc_log->info("Starting PLMN search\n"); - uint32_t nof_plmns = 0; - phy_interface_rrc_lte::cell_search_ret_t ret; - do { - ret = cell_search(); - if (ret.found == phy_interface_rrc_lte::cell_search_ret_t::CELL_FOUND) { - if (serving_cell->has_sib1()) { - // Save PLMN and TAC to NAS - for (uint32_t i = 0; i < serving_cell->nof_plmns(); i++) { - if (nof_plmns < MAX_FOUND_PLMNS) { - found_plmns[nof_plmns].plmn_id = serving_cell->get_plmn(i); - found_plmns[nof_plmns].tac = serving_cell->get_tac(); - nof_plmns++; - } else { - rrc_log->error("No more space for plmns (%d)\n", nof_plmns); - } - } - } else { - rrc_log->error("SIB1 not acquired\n"); - } - } - } while (ret.last_freq == phy_interface_rrc_lte::cell_search_ret_t::MORE_FREQS && - ret.found != phy_interface_rrc_lte::cell_search_ret_t::ERROR); - - // Process all pending measurements before returning - process_phy_meas(); - - pthread_mutex_unlock(&mutex); - - if (ret.found == phy_interface_rrc_lte::cell_search_ret_t::ERROR) { - return -1; - } else { - return nof_plmns; +bool rrc::plmn_search() +{ + if (not plmn_searcher.launch(this)) { + rrc_log->error("Unable to initiate PLMN search\n"); + return false; } + callback_list.defer_proc(plmn_searcher); + return true; } /* This is the NAS interface. When NAS requests to select a PLMN we have to @@ -425,104 +351,12 @@ void rrc::plmn_select(srslte::plmn_id_t plmn_id) */ bool rrc::connection_request(srslte::establishment_cause_t cause, srslte::unique_byte_buffer_t dedicated_info_nas) { - - if (!plmn_is_selected) { - rrc_log->error("Trying to connect but PLMN not selected.\n"); - return false; - } - - if (state != RRC_STATE_IDLE) { - rrc_log->warning("Requested RRC connection establishment while not in IDLE\n"); - return false; - } - - if (mac_timers->timer_get(t302)->is_running()) { - rrc_log->info("Requested RRC connection establishment while T302 is running\n"); - nas->set_barring(nas_interface_rrc::BARRING_MO_DATA); + if (not conn_req_proc.launch(this, cause, std::move(dedicated_info_nas))) { + rrc_log->error("Failed to initiate connection request procedure\n"); return false; } - - bool ret = false; - - pthread_mutex_lock(&mutex); - - rrc_log->info("Initiation of Connection establishment procedure\n"); - - // Perform cell selection & reselection for the selected PLMN - cs_ret_t cs_ret = cell_selection(); - - // .. and SI acquisition - if (phy->cell_is_camping()) { - - // Set default configurations - set_phy_default(); - set_mac_default(); - - // CCCH configuration applied already at start - // timeAlignmentCommon applied in configure_serving_cell - - rrc_log->info("Configuring serving cell...\n"); - if (configure_serving_cell()) { - - mac_timers->timer_get(t300)->reset(); - mac_timers->timer_get(t300)->run(); - - // Send connectionRequest message to lower layers - establishment_cause_e asn1cause((establishment_cause_e::options)cause); - send_con_request(asn1cause); - - // Save dedicatedInfoNAS SDU - if (this->dedicated_info_nas.get()) { - rrc_log->warning("Received a new dedicatedInfoNAS SDU but there was one still in queue. Removing it\n"); - } - this->dedicated_info_nas = std::move(dedicated_info_nas); - - // Wait until t300 stops due to RRCConnectionSetup/Reject or expiry - while (mac_timers->timer_get(t300)->is_running()) { - usleep(1000); - } - - if (state == RRC_STATE_CONNECTED) { - // Received ConnectionSetup - ret = true; - } else if (mac_timers->timer_get(t300)->is_expired()) { - // T300 is expired: 5.3.3.6 - rrc_log->info("Timer T300 expired: ConnectionRequest timed out\n"); - mac->reset(); - set_mac_default(); - rlc->reestablish(); - } else { - // T300 is stopped but RRC not Connected is because received Reject: Section 5.3.3.8 - rrc_log->info("Timer T300 stopped: Received ConnectionReject\n"); - mac->reset(); - set_mac_default(); - } - - } else { - rrc_log->error("Configuring serving cell\n"); - } - } else { - switch(cs_ret) { - case SAME_CELL: - rrc_log->warning("Did not reselect cell but serving cell is out-of-sync.\n"); - serving_cell->in_sync = false; - break; - case CHANGED_CELL: - rrc_log->warning("Selected a new cell but could not camp on. Setting out-of-sync.\n"); - serving_cell->in_sync = false; - break; - default: - rrc_log->warning("Could not find any suitable cell to connect\n"); - } - } - - if (!ret) { - rrc_log->warning("Could not establish connection. Deallocating dedicatedInfoNAS PDU\n"); - this->dedicated_info_nas.reset(); - } - - pthread_mutex_unlock(&mutex); - return ret; + callback_list.defer_proc(conn_req_proc); + return true; } void rrc::set_ue_identity(srslte::s_tmsi_t s_tmsi) @@ -533,45 +367,6 @@ void rrc::set_ue_identity(srslte::s_tmsi_t s_tmsi) "Set ue-Identity to 0x%" PRIu64 ":0x%" PRIu64 "\n", (uint64_t)ue_identity.mmec, (uint64_t)ue_identity.m_tmsi); } -/* Retrieves all required SIB or configures them if already retrieved before - */ -bool rrc::configure_serving_cell() { - - if (!phy->cell_is_camping()) { - rrc_log->error("Trying to configure Cell while not camping on it\n"); - return false; - } - serving_cell->has_mcch = false; - // Obtain the SIBs if not available or apply the configuration if available - for (uint32_t i = 0; i < NOF_REQUIRED_SIBS; i++) { - if (!serving_cell->has_sib(required_sibs[i])) { - rrc_log->info("Cell has no SIB%d. Obtaining SIB%d\n", required_sibs[i]+1, required_sibs[i]+1); - if (!si_acquire(required_sibs[i])) { - rrc_log->info("Timeout while acquiring SIB%d\n", required_sibs[i]+1); - if (required_sibs[i] < 2) { - return false; - } - } - } else { - rrc_log->info("Cell has SIB%d\n", required_sibs[i]+1); - switch(required_sibs[i]) { - case 1: - handle_sib2(); - break; - case 12: - handle_sib13(); - break; - } - } - } - return true; -} - - - - - - /******************************************************************************* * * @@ -676,9 +471,7 @@ void rrc::out_of_sync() // Recovery of physical layer problems (5.3.11.2) void rrc::in_sync() { - // CAUTION: We do not lock in this function since they are called from real-time threads - serving_cell->in_sync = true; if (mac_timers->timer_get(t310)->is_running()) { n311_cnt++; @@ -704,123 +497,6 @@ void rrc::in_sync() - -/******************************************************************************* -* -* -* -* System Information Acquisition procedure -* -* -* -*******************************************************************************/ - - -// Determine SI messages scheduling as in 36.331 5.2.3 Acquisition of an SI message -uint32_t rrc::sib_start_tti(uint32_t tti, uint32_t period, uint32_t offset, uint32_t sf) { - return (period*10*(1+tti/(period*10))+(offset*10)+sf)%10240; // the 1 means next opportunity -} - -/* Implemnets the SI acquisition procedure - * Configures the MAC/PHY scheduling to retrieve SI messages. The function is blocking and will not - * return until SIB is correctly received or timeout - */ -bool rrc::si_acquire(uint32_t sib_index) -{ - uint32_t tti = 0; - uint32_t si_win_start=0, si_win_len=0; - uint16_t period = 0; - uint32_t sched_index = 0; - uint32_t x, sf, offset; - - uint32_t last_win_start = 0; - uint32_t timeout = 0; - - while(timeout < SIB_SEARCH_TIMEOUT_MS && !serving_cell->has_sib(sib_index)) { - - bool instruct_phy = false; - - if (sib_index == 0) { - - // Instruct MAC to look for SIB1 - tti = mac->get_current_tti(); - si_win_start = sib_start_tti(tti, 2, 0, 5); - if (last_win_start == 0 || - (srslte_tti_interval(tti, last_win_start) >= 20 && srslte_tti_interval(tti, last_win_start) < 1000)) { - - last_win_start = si_win_start; - si_win_len = 1; - instruct_phy = true; - } - period = 20; - sched_index = 0; - } else { - // Instruct MAC to look for SIB2..13 - if (serving_cell->has_sib1()) { - - asn1::rrc::sib_type1_s* sib1 = serving_cell->sib1ptr(); - - // SIB2 scheduling - if (sib_index == 1) { - period = sib1->sched_info_list[0].si_periodicity.to_number(); - sched_index = 0; - } else { - // SIB3+ scheduling Section 5.2.3 - if (sib_index >= 2) { - bool found = false; - for (uint32_t i = 0; i < sib1->sched_info_list.size() && !found; i++) { - for (uint32_t j = 0; j < sib1->sched_info_list[i].sib_map_info.size() && !found; j++) { - if (sib1->sched_info_list[i].sib_map_info[j].to_number() == sib_index + 1) { - period = sib1->sched_info_list[i].si_periodicity.to_number(); - sched_index = i; - found = true; - } - } - } - if (!found) { - rrc_log->info("Could not find SIB%d scheduling in SIB1\n", sib_index+1); - return false; - } - } - } - si_win_len = sib1->si_win_len.to_number(); - x = sched_index * si_win_len; - sf = x % 10; - offset = x / 10; - - tti = mac->get_current_tti(); - si_win_start = sib_start_tti(tti, period, offset, sf); - - if (last_win_start == 0 || (srslte_tti_interval(tti, last_win_start) > period * 5 && - srslte_tti_interval(tti, last_win_start) < 1000)) { - last_win_start = si_win_start; - instruct_phy = true; - } - } else { - rrc_log->error("Trying to receive SIB%d but SIB1 not received\n", sib_index + 1); - } - } - - // Instruct MAC to decode SIB - if (instruct_phy && !serving_cell->has_sib(sib_index)) { - mac->bcch_start_rx(si_win_start, si_win_len); - rrc_log->info("Instructed MAC to search for SIB%d, win_start=%d, win_len=%d, period=%d, sched_index=%d\n", - sib_index+1, si_win_start, si_win_len, period, sched_index); - } - usleep(1000); - timeout++; - } - return serving_cell->has_sib(sib_index); -} - - - - - - - - - /******************************************************************************* * * @@ -831,113 +507,10 @@ bool rrc::si_acquire(uint32_t sib_index) * *******************************************************************************/ -/* Searches for a cell in the current frequency and retrieves SIB1 if not retrieved yet - */ -phy_interface_rrc_lte::cell_search_ret_t rrc::cell_search() -{ - phy_interface_rrc_lte::phy_cell_t new_cell; - - phy_interface_rrc_lte::cell_search_ret_t ret = phy->cell_search(&new_cell); - - switch(ret.found) { - case phy_interface_rrc_lte::cell_search_ret_t::CELL_FOUND: - rrc_log->info("Cell found in this frequency. Setting new serving cell...\n"); - - // Create cell with NaN RSRP. Will be updated by new_phy_meas() during SIB search. - if (!add_neighbour_cell(new_cell, NAN)) { - rrc_log->info("No more space for neighbour cells\n"); - break; - } - set_serving_cell(new_cell); - - if (phy->cell_is_camping()) { - if (!serving_cell->has_sib1()) { - rrc_log->info("Cell has no SIB1. Obtaining SIB1\n"); - if (!si_acquire(0)) { - rrc_log->error("Timeout while acquiring SIB1\n"); - } - } else { - rrc_log->info("Cell has SIB1\n"); - } - } else { - rrc_log->warning("Could not camp on found cell. Trying next one...\n"); - } - break; - case phy_interface_rrc_lte::cell_search_ret_t::CELL_NOT_FOUND: - rrc_log->info("No cells found.\n"); - break; - case phy_interface_rrc_lte::cell_search_ret_t::ERROR: - rrc_log->error("In cell search. Finishing PLMN search\n"); - break; - } - return ret; -} - -/* Cell selection procedure 36.304 5.2.3 - * Select the best cell to camp on among the list of known cells - */ -rrc::cs_ret_t rrc::cell_selection() -{ - // Neighbour cells are sorted in descending order of RSRP - for (uint32_t i = 0; i < neighbour_cells.size(); i++) { - if (/*TODO: CHECK that PLMN matches. Currently we don't receive SIB1 of neighbour cells - * neighbour_cells[i]->plmn_equals(selected_plmn_id) && */ - neighbour_cells[i]->in_sync) // matches S criteria - { - // If currently connected, verify cell selection criteria - if (!serving_cell->in_sync || - (cell_selection_criteria(neighbour_cells[i]->get_rsrp()) && - neighbour_cells[i]->get_rsrp() > serving_cell->get_rsrp() + 5)) - { - // Try to select Cell - set_serving_cell(i); - rrc_log->info("Selected cell idx=%d, PCI=%d, EARFCN=%d\n", - i, serving_cell->get_pci(), serving_cell->get_earfcn()); - rrc_log->console("Selected cell PCI=%d, EARFCN=%d\n", - serving_cell->get_pci(), serving_cell->get_earfcn()); - - if (phy->cell_select(&serving_cell->phy_cell)) { - if (configure_serving_cell()) { - rrc_log->info("Selected and configured cell successfully\n"); - return CHANGED_CELL; - } else { - rrc_log->error("While configuring serving cell\n"); - } - } else { - serving_cell->in_sync = false; - rrc_log->warning("Could not camp on selected cell\n"); - } - } - } - } - if (serving_cell->in_sync) { - if (!phy->cell_is_camping()) { - rrc_log->info("Serving cell is in-sync but not camping. Selecting it...\n"); - if (phy->cell_select(&serving_cell->phy_cell)) { - rrc_log->info("Selected serving cell OK.\n"); - } else { - serving_cell->in_sync = false; - rrc_log->error("Could not camp on serving cell.\n"); - } - } - return SAME_CELL; - } - // If can not find any suitable cell, search again - rrc_log->info("Cell selection and reselection in IDLE did not find any suitable cell. Searching again\n"); - // If can not camp on any cell, search again for new cells - phy_interface_rrc_lte::cell_search_ret_t ret = cell_search(); - - return (ret.found == phy_interface_rrc_lte::cell_search_ret_t::CELL_FOUND) ? CHANGED_CELL : NO_CELL; -} - // Cell selection criteria Section 5.2.3.2 of 36.304 bool rrc::cell_selection_criteria(float rsrp, float rsrq) { - if (get_srxlev(rsrp) > 0 || !serving_cell->has_sib3()) { - return true; - } else { - return false; - } + return (get_srxlev(rsrp) > 0 || !serving_cell->has_sib3()); } float rrc::get_srxlev(float Qrxlevmeas) { @@ -1250,7 +823,9 @@ void rrc::ra_problem() { void rrc::max_retx_attempted() { //TODO: Handle the radio link failure rrc_log->warning("Max RLC reTx attempted\n"); - radio_link_failure(); + cmd_msg_t msg; + msg.command = cmd_msg_t::RLF; + cmd_q.push(std::move(msg)); } void rrc::timer_expired(uint32_t timeout_id) { @@ -1259,13 +834,13 @@ void rrc::timer_expired(uint32_t timeout_id) { radio_link_failure(); } else if (timeout_id == t311) { rrc_log->info("Timer T311 expired: Going to RRC IDLE\n"); - go_idle = true; + start_go_idle(); } else if (timeout_id == t301) { if (state == RRC_STATE_IDLE) { rrc_log->info("Timer T301 expired: Already in IDLE.\n"); } else { rrc_log->info("Timer T301 expired: Going to RRC IDLE\n"); - go_idle = true; + start_go_idle(); } } else if (timeout_id == t302) { rrc_log->info("Timer T302 expired. Informing NAS about barrier alleviation\n"); @@ -1298,7 +873,7 @@ void rrc::timer_expired(uint32_t timeout_id) { * *******************************************************************************/ -void rrc::send_con_request(asn1::rrc::establishment_cause_e cause) +void rrc::send_con_request(srslte::establishment_cause_t cause) { rrc_log->debug("Preparing RRC Connection Request\n"); @@ -1319,7 +894,7 @@ void rrc::send_con_request(asn1::rrc::establishment_cause_e cause) } rrc_conn_req->ue_id.random_value().from_number(random_id); } - rrc_conn_req->establishment_cause = cause; + rrc_conn_req->establishment_cause = (asn1::rrc::establishment_cause_opts::options)cause; send_ul_ccch_msg(ul_ccch_msg); } @@ -1562,8 +1137,8 @@ bool rrc::ho_prepare() return true; } -void rrc::ho_ra_completed(bool ra_successful) { - +void rrc::ho_ra_completed(bool ra_successful) +{ if (pending_mob_reconf) { asn1::rrc::rrc_conn_recfg_r8_ies_s* mob_reconf_r8 = &mob_reconf.crit_exts.c1().rrc_conn_recfg_r8(); if (ra_successful) { @@ -1603,10 +1178,34 @@ bool rrc::con_reconfig_ho(asn1::rrc::rrc_conn_recfg_s* reconfig) mob_reconf = *reconfig; pending_mob_reconf = true; - ho_start = true; + start_ho(); return true; } +void rrc::start_ho() +{ + callback_list.defer_task([this]() { + if (state != RRC_STATE_CONNECTED) { + rrc_log->info("HO interrupted, since RRC is no longer in connected state\n"); + return srslte::proc_outcome_t::success; + } + if (not ho_prepare()) { + con_reconfig_failed(); + return srslte::proc_outcome_t::error; + } + return srslte::proc_outcome_t::success; + }); +} + +void rrc::start_go_idle() +{ + if (not idle_setter.launch(this)) { + rrc_log->info("Failed to set RRC to IDLE\n"); + return; + } + callback_list.defer_proc(idle_setter); +} + // Handle RRC Reconfiguration without MobilityInformation Section 5.3.5.3 bool rrc::con_reconfig(asn1::rrc::rrc_conn_recfg_s* reconfig) { @@ -1687,7 +1286,7 @@ void rrc::con_reconfig_failed() // Start the Reestablishment Procedure init_con_restablish_request(asn1::rrc::reest_cause_e::recfg_fail); } else { - go_idle = true; + start_go_idle(); } } @@ -1712,7 +1311,7 @@ void rrc::handle_rrc_con_reconfig(uint32_t lcid, asn1::rrc::rrc_conn_recfg_s* re void rrc::rrc_connection_release() { // Save idleModeMobilityControlInfo, etc. rrc_log->console("Received RRC Connection Release\n"); - go_idle = true; + start_go_idle(); } /* Actions upon leaving RRC_CONNECTED 5.3.12 */ @@ -1722,7 +1321,6 @@ void rrc::leave_connected() rrc_log->info("Leaving RRC_CONNECTED state\n"); m_reest_cause = asn1::rrc::reest_cause_e::nulltype; state = RRC_STATE_IDLE; - rlc_flush_counter = 0; drb_up = false; security_is_activated = false; measurements.reset(); @@ -1782,7 +1380,7 @@ void rrc::init_con_restablish_request(asn1::rrc::reest_cause_e cause) // 3GPP 36.331 Section 5.3.7.1 // If AS security has not been activated, the UE does not initiate the procedure but instead // moves to RRC_IDLE directly - go_idle = true; + start_go_idle(); } } @@ -1865,6 +1463,49 @@ void rrc::proc_con_restablish_request() } } +void rrc::start_cell_reselection() +{ + if (neighbour_cells.empty() and serving_cell->in_sync and phy->cell_is_camping()) { + // don't bother with cell selection if there are no neighbours and we are already camping + return; + } + + if (not cell_selector.launch(this)) { + rrc_log->error("Failed to initiate a Cell Selection procedure...\n"); + return; + } + + rrc_log->info("Cell Reselection - Starting..."); + callback_list.defer_task([this]() { + if (cell_selector.run()) { + return srslte::proc_outcome_t::yield; + } + cell_selection_proc ret = cell_selector.pop(); + if (ret.is_error()) { + rrc_log->error("Cell Reselection - Error while selecting a cell\n"); + return srslte::proc_outcome_t::error; + } else { + switch (ret.get_cs_result()) { + case cs_result_t::changed_cell: + // New cell has been selected, start receiving PCCH + mac->pcch_start_rx(); + break; + case cs_result_t::no_cell: + rrc_log->warning("Could not find any cell to camp on\n"); + break; + case cs_result_t::same_cell: + if (!phy->cell_is_camping()) { + rrc_log->warning("Did not reselect cell but serving cell is out-of-sync.\n"); + serving_cell->in_sync = false; + } + break; + } + } + rrc_log->info("Cell Reselection - Finished successfully"); + return srslte::proc_outcome_t::success; + }); +} + /******************************************************************************* * * @@ -1881,6 +1522,14 @@ void rrc::write_pdu_bcch_bch(unique_byte_buffer_t pdu) } void rrc::write_pdu_bcch_dlsch(unique_byte_buffer_t pdu) +{ + cmd_msg_t msg; + msg.command = cmd_msg_t::PDU_BCCH_DLSCH; + msg.pdu = std::move(pdu); + cmd_q.push(std::move(msg)); +} + +void rrc::parse_pdu_bcch_dlsch(unique_byte_buffer_t pdu) { // Stop BCCH search after successful reception of 1 BCCH block mac->bcch_stop_rx(); @@ -2059,81 +1708,76 @@ void rrc::write_pdu_pcch(unique_byte_buffer_t pdu) cmd_q.push(std::move(msg)); } +void rrc::paging_completed(bool outcome) +{ + pcch_processor.trigger_event(process_pcch_proc::paging_complete{outcome}); +} + void rrc::process_pcch(unique_byte_buffer_t pdu) { - if (pdu->N_bytes > 0 && pdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES) { - pcch_msg_s pcch_msg; - asn1::bit_ref bref(pdu->msg, pdu->N_bytes); - if (pcch_msg.unpack(bref) != asn1::SRSASN_SUCCESS or pcch_msg.msg.type().value != pcch_msg_type_c::types_opts::c1) { - rrc_log->error("Failed to unpack PCCH message\n"); - return; - } + if (pdu->N_bytes <= 0 or pdu->N_bytes >= SRSLTE_MAX_BUFFER_SIZE_BITS) { + rrc_log->error_hex(pdu->buffer, pdu->N_bytes, "Dropping PCCH message with %d B\n", pdu->N_bytes); + return; + } - log_rrc_message("PCCH", Rx, pdu.get(), pcch_msg); + pcch_msg_s pcch_msg; + asn1::bit_ref bref(pdu->msg, pdu->N_bytes); + if (pcch_msg.unpack(bref) != asn1::SRSASN_SUCCESS or pcch_msg.msg.type().value != pcch_msg_type_c::types_opts::c1) { + rrc_log->error("Failed to unpack PCCH message\n"); + return; + } - paging_s* paging = &pcch_msg.msg.c1().paging(); - if (paging->paging_record_list.size() > ASN1_RRC_MAX_PAGE_REC) { - paging->paging_record_list.resize(ASN1_RRC_MAX_PAGE_REC); - } + log_rrc_message("PCCH", Rx, pdu.get(), pcch_msg); - if (not ue_identity_configured) { - rrc_log->warning("Received paging message but no ue-Identity is configured\n"); - return; - } + if (not ue_identity_configured) { + rrc_log->warning("Received paging message but no ue-Identity is configured\n"); + return; + } - for (uint32_t i = 0; i < paging->paging_record_list.size(); i++) { - s_tmsi_t s_tmsi_paged = make_s_tmsi_t(paging->paging_record_list[i].ue_id.s_tmsi()); - rrc_log->info("Received paging (%d/%d) for UE %" PRIu64 ":%" PRIu64 "\n", - i + 1, - paging->paging_record_list.size(), - (uint64_t)s_tmsi_paged.mmec, - (uint64_t)s_tmsi_paged.m_tmsi); - if (ue_identity.mmec == s_tmsi_paged.mmec && ue_identity.m_tmsi == s_tmsi_paged.m_tmsi) { - if (RRC_STATE_IDLE == state) { - rrc_log->info("S-TMSI match in paging message\n"); - rrc_log->console("S-TMSI match in paging message\n"); - nas->paging(&s_tmsi_paged); - } else { - rrc_log->warning("Received paging while in CONNECT\n"); - } - } else { - rrc_log->info("Received paging for unknown identity\n"); - } - } + paging_s* paging = &pcch_msg.msg.c1().paging(); + if (paging->paging_record_list.size() > ASN1_RRC_MAX_PAGE_REC) { + paging->paging_record_list.resize(ASN1_RRC_MAX_PAGE_REC); + } - if (paging->sys_info_mod_present) { - rrc_log->info("Received System Information notification update request.\n"); - // invalidate and then update all SIBs of serving cell - serving_cell->reset_sibs(); - if (configure_serving_cell()) { - rrc_log->info("All SIBs of serving cell obtained successfully\n"); - } else { - rrc_log->error("While obtaining SIBs of serving cell.\n"); - } - } - } else { - rrc_log->error_hex(pdu->buffer, pdu->N_bytes, "Dropping PCCH message with %d B\n", pdu->N_bytes); + if (not pcch_processor.launch(this, *paging)) { + rrc_log->error("Failed to launch process PCCH procedure\n"); + return; } + + // we do not care about the outcome + callback_list.defer_proc(pcch_processor); } void rrc::write_pdu_mch(uint32_t lcid, srslte::unique_byte_buffer_t pdu) { - if (pdu->N_bytes > 0 && pdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES) { - //TODO: handle MCCH notifications and update MCCH - if (0 == lcid && !serving_cell->has_mcch) { - asn1::bit_ref bref(pdu->msg, pdu->N_bytes); - if (serving_cell->mcch.unpack(bref) != asn1::SRSASN_SUCCESS or - serving_cell->mcch.msg.type().value != mcch_msg_type_c::types_opts::c1) { - rrc_log->error("Failed to unpack MCCH message\n"); - return; - } - serving_cell->has_mcch = true; - phy->set_config_mbsfn_mcch(&serving_cell->mcch); - log_rrc_message("MCH", Rx, pdu.get(), serving_cell->mcch); - cmd_msg_t msg; - msg.command = cmd_msg_t::MBMS_START; - cmd_q.push(std::move(msg)); - } + if (pdu->N_bytes <= 0 or pdu->N_bytes >= SRSLTE_MAX_BUFFER_SIZE_BITS) { + return; + } + // TODO: handle MCCH notifications and update MCCH + if (0 != lcid or serving_cell->has_mcch) { + return; + } + cmd_msg_t msg; + msg.command = cmd_msg_t::PDU_MCH; + msg.pdu = std::move(pdu); + msg.lcid = lcid; + cmd_q.push(std::move(msg)); +} + +void rrc::parse_pdu_mch(uint32_t lcid, srslte::unique_byte_buffer_t pdu) +{ + asn1::bit_ref bref(pdu->msg, pdu->N_bytes); + if (serving_cell->mcch.unpack(bref) != asn1::SRSASN_SUCCESS or + serving_cell->mcch.msg.type().value != mcch_msg_type_c::types_opts::c1) { + rrc_log->error("Failed to unpack MCCH message\n"); + return; + } + serving_cell->has_mcch = true; + phy->set_config_mbsfn_mcch(&serving_cell->mcch); + log_rrc_message("MCH", Rx, pdu.get(), serving_cell->mcch); + if (args.mbms_service_id >= 0) { + rrc_log->info("Attempting to auto-start MBMS service %d\n", args.mbms_service_id); + mbms_service_start(args.mbms_service_id, args.mbms_service_port); } } @@ -2212,26 +1856,6 @@ void rrc::write_sdu(srslte::unique_byte_buffer_t sdu) void rrc::write_pdu(uint32_t lcid, unique_byte_buffer_t pdu) { - // If the message contains a ConnectionSetup, acknowledge the transmission to avoid blocking of paging procedure - if (lcid == 0) { - // FIXME: We unpack and process this message twice to check if it's ConnectionSetup - asn1::bit_ref bref(pdu->msg, pdu->N_bytes); - asn1::rrc::dl_ccch_msg_s dl_ccch_msg; - if (dl_ccch_msg.unpack(bref) != asn1::SRSASN_SUCCESS or - dl_ccch_msg.msg.type().value != dl_ccch_msg_type_c::types_opts::c1) { - rrc_log->error("Failed to unpack DL-CCCH message\n"); - return; - } - if (dl_ccch_msg.msg.c1().type() == dl_ccch_msg_type_c::c1_c_::types::rrc_conn_setup) { - // Must enter CONNECT before stopping T300 - state = RRC_STATE_CONNECTED; - - mac_timers->timer_get(t300)->stop(); - mac_timers->timer_get(t302)->stop(); - rrc_log->console("RRC Connected\n"); - } - } - // add PDU to command queue cmd_msg_t msg; msg.pdu = std::move(pdu); @@ -2284,7 +1908,7 @@ void rrc::parse_dl_ccch(unique_byte_buffer_t pdu) } else { // Perform the actions upon expiry of T302 if wait time is zero nas->set_barring(nas_interface_rrc::BARRING_NONE); - go_idle = true; + start_go_idle(); } } break; case dl_ccch_msg_type_c::c1_c_::types::rrc_conn_setup: @@ -2299,7 +1923,7 @@ void rrc::parse_dl_ccch(unique_byte_buffer_t pdu) /* Reception of RRCConnectionReestablishmentReject 5.3.7.8 */ case dl_ccch_msg_type_c::c1_c_::types::rrc_conn_reest_reject: rrc_log->console("Reestablishment Reject\n"); - go_idle = true; + start_go_idle(); break; default: rrc_log->error("The provided DL-CCCH message type is not recognized\n"); @@ -3095,6 +2719,12 @@ void rrc::apply_scell_config(asn1::rrc::rrc_conn_recfg_r8_ies_s* reconfig_r8) void rrc::handle_con_setup(rrc_conn_setup_s* setup) { + // Must enter CONNECT before stopping T300 + state = RRC_STATE_CONNECTED; + mac_timers->timer_get(t300)->stop(); + mac_timers->timer_get(t302)->stop(); + rrc_log->console("RRC Connected\n"); + // Apply the Radio Resource configuration apply_rr_config_dedicated(&setup->crit_exts.c1().rrc_conn_setup_r8().rr_cfg_ded); diff --git a/srsue/src/stack/rrc/rrc_procedures.cc b/srsue/src/stack/rrc/rrc_procedures.cc new file mode 100644 index 000000000..061e75a78 --- /dev/null +++ b/srsue/src/stack/rrc/rrc_procedures.cc @@ -0,0 +1,795 @@ +/* + * Copyright 2013-2019 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/rrc_procedures.h" +#include // for printing uint64_t + +#define Error(fmt, ...) rrc_ptr->rrc_log->error("Proc \"%s\" - " fmt, name(), ##__VA_ARGS__) +#define Warning(fmt, ...) rrc_ptr->rrc_log->warning("Proc \"%s\" - " fmt, name(), ##__VA_ARGS__) +#define Info(fmt, ...) rrc_ptr->rrc_log->info("Proc \"%s\" - " fmt, name(), ##__VA_ARGS__) +#define Debug(fmt, ...) rrc_ptr->rrc_log->debug("Proc \"%s\" - " fmt, name(), ##__VA_ARGS__) + +namespace srsue { + +using srslte::proc_outcome_t; + +/************************************** + * Cell Search Procedure + *************************************/ + +/* Searches for a cell in the current frequency and retrieves SIB1 if not retrieved yet */ +proc_outcome_t rrc::cell_search_proc::init(srsue::rrc* parent_) +{ + rrc_ptr = parent_; + log_h = parent_->rrc_log; + + Info("Starting...\n"); + state = state_t::phy_cell_search; + return proc_outcome_t::repeat; +} + +/* Implements the SI acquisition procedure. Configures MAC/PHY scheduling to retrieve SI messages.*/ +proc_outcome_t rrc::cell_search_proc::step() +{ + if (state == state_t::phy_cell_search) { + // Blocks waiting for result of phy cell search + phy_interface_rrc_lte::phy_cell_t new_cell; + + // BLOCKING CALL + cs_ret = rrc_ptr->phy->cell_search(&new_cell); + + switch (cs_ret.found) { + case phy_interface_rrc_lte::cell_search_ret_t::CELL_FOUND: { + return handle_cell_found(new_cell); + } + case phy_interface_rrc_lte::cell_search_ret_t::CELL_NOT_FOUND: + Info("No cells found.\n"); + // do nothing + return proc_outcome_t::success; + case phy_interface_rrc_lte::cell_search_ret_t::ERROR: + Error("Error while performing cell search\n"); + // TODO: check what errors can happen (currently not handled in our code) + return proc_outcome_t::error; + } + } else if (state == state_t::si_acquire) { + if (not rrc_ptr->si_acquirer.run()) { + // SI Acquire has completed + si_acquire_proc ret = rrc_ptr->si_acquirer.pop(); + if (ret.is_error()) { + Error("Failed to trigger SI acquire for SIB0\n"); + return proc_outcome_t::error; + } else { + // if(parent->serving_cell->has_sib(0)) { + // } + return proc_outcome_t::success; + } + } + } + return proc_outcome_t::yield; +} + +proc_outcome_t rrc::cell_search_proc::handle_cell_found(const phy_interface_rrc_lte::phy_cell_t& new_cell) +{ + Info("Cell found in this frequency. Setting new serving cell...\n"); + + // Create a cell with NaN RSRP. Will be updated by new_phy_meas() during SIB search. + if (not rrc_ptr->add_neighbour_cell(new_cell, NAN)) { + Info("No more space for neighbour cells\n"); + return proc_outcome_t::success; + } + + rrc_ptr->set_serving_cell(new_cell); + + if (not rrc_ptr->phy->cell_is_camping()) { + log_h->warning("Could not camp on found cell.\n"); + return proc_outcome_t::error; + } + + if (rrc_ptr->serving_cell->has_sib1()) { + Info("Cell has SIB1\n"); + // What do we do???? + return proc_outcome_t::success; + } + + if (not rrc_ptr->si_acquirer.launch(rrc_ptr, 0)) { + // disallow concurrent si_acquire + Error("SI Acquire is already running...\n"); + return proc_outcome_t::error; + } + + // instruct MAC to look for SIB1 + Info("Cell has no SIB1. Obtaining SIB1...\n"); + state = state_t::si_acquire; + return proc_outcome_t::repeat; +} + +/************************************** + * SI Acquire Procedure + *************************************/ + +proc_outcome_t rrc::si_acquire_proc::init(rrc* parent_, uint32_t sib_index_) +{ + rrc_ptr = parent_; + log_h = parent_->rrc_log; + + Info("Starting SI Acquire procedure for SIB%d\n", sib_index_ + 1); + sib_index = sib_index_; + start_tti = rrc_ptr->mac->get_current_tti(); + + // set period/sched_index + if (sib_index == 0) { + period = 20; + sched_index = 0; + } else { + // Instruct MAC to look for SIB2..13 + if (not rrc_ptr->serving_cell->has_sib1()) { + Error("Trying to acquire SIB%d but SIB1 not received yet\n", sib_index + 1); + return proc_outcome_t::error; + } + asn1::rrc::sib_type1_s* sib1 = rrc_ptr->serving_cell->sib1ptr(); + + if (sib_index == 1) { + // SIB2 scheduling + period = sib1->sched_info_list[0].si_periodicity.to_number(); + sched_index = 0; + } else { + // SIB3+ scheduling Section 5.2.3 + bool found = false; + for (uint32_t i = 0; i < sib1->sched_info_list.size() && not found; ++i) { + for (uint32_t j = 0; j < sib1->sched_info_list[i].sib_map_info.size() && not found; ++j) { + if (sib1->sched_info_list[i].sib_map_info[j].to_number() == sib_index + 1) { + period = sib1->sched_info_list[i].si_periodicity.to_number(); + sched_index = i; + found = true; + } + } + } + if (not found) { + Info("Could not find SIB%d scheduling in SIB1\n", sib_index + 1); + return proc_outcome_t::success; + } + } + } + + return proc_outcome_t::repeat; +} + +proc_outcome_t rrc::si_acquire_proc::step() +{ + uint32_t tti = rrc_ptr->mac->get_current_tti(); + uint32_t tti_diff1 = srslte_tti_interval(tti, start_tti); + bool has_timeout = tti_diff1 >= SIB_SEARCH_TIMEOUT_MS and tti_diff1 < 10240 / 2; + if (has_timeout or rrc_ptr->serving_cell->has_sib(sib_index)) { + if (rrc_ptr->serving_cell->has_sib(sib_index)) { + Info("SIB%d acquired successfully\n", sib_index + 1); + return proc_outcome_t::success; + } else { + Error("Timeout while acquiring SIB1\n"); + return proc_outcome_t::error; + } + } else { + + uint32_t tti_diff2 = srslte_tti_interval(tti, last_win_start); + + // If first time or inside retry window, trigger MAC to decode SIB + uint32_t retry_period = (sib_index == 0) ? 20 : period * 5; + if (last_win_start == 0 or (tti_diff2 > retry_period and tti_diff2 < 1000)) { + + // compute win start and len + uint32_t si_win_start, si_win_len; + if (sib_index == 0) { + si_win_len = 1; + si_win_start = sib_start_tti(tti, 2, 0, 5); + } else { + asn1::rrc::sib_type1_s* sib1 = rrc_ptr->serving_cell->sib1ptr(); + + si_win_len = sib1->si_win_len.to_number(); + uint32_t x = sched_index * si_win_len; + uint32_t sf = x % 10, offset = x / 10; + si_win_start = sib_start_tti(tti, period, offset, sf); + } + last_win_start = si_win_start; + + // Instruct MAC to decode SIB + rrc_ptr->mac->bcch_start_rx(si_win_start, si_win_len); + Info("Instructed MAC to search for SIB%d, win_start=%d, win_len=%d, period=%d, sched_index=%d\n", + sib_index + 1, + si_win_start, + si_win_len, + period, + sched_index); + } + } + + return proc_outcome_t::yield; +} + +// Determine SI messages scheduling as in 36.331 5.2.3 Acquisition of an SI message +uint32_t rrc::si_acquire_proc::sib_start_tti(uint32_t tti, uint32_t period, uint32_t offset, uint32_t sf) +{ + return (period * 10 * (1 + tti / (period * 10)) + (offset * 10) + sf) % 10240; // the 1 means next opportunity +} + +/************************************** + * Serving Cell Config Procedure + *************************************/ + +/* + * Retrieves all required SIB or configures them if already retrieved before + */ +proc_outcome_t rrc::serving_cell_config_proc::init(srsue::rrc* parent_, const std::vector& required_sibs_) +{ + rrc_ptr = parent_; + log_h = parent_->rrc_log; + required_sibs = required_sibs_; + + Info("Starting a Serving Cell Configuration Procedure\n"); + + if (not rrc_ptr->phy->cell_is_camping()) { + Error("Trying to configure Cell while not camping on it\n"); + return proc_outcome_t::error; + } + + rrc_ptr->serving_cell->has_mcch = false; + + req_idx = 0; + search_state = search_state_t::next_sib; + return proc_outcome_t::repeat; +} + +proc_outcome_t rrc::serving_cell_config_proc::step() +{ + if (search_state == search_state_t::next_sib) { + // Obtain the SIBs if not available or apply the configuration if available + for (; req_idx < required_sibs.size(); req_idx++) { + uint32_t required_sib = required_sibs[req_idx]; + + if (not rrc_ptr->serving_cell->has_sib(required_sib)) { + Info("Cell has no SIB%d. Obtaining SIB%d\n", required_sib + 1, required_sib + 1); + if (not rrc_ptr->si_acquirer.launch(rrc_ptr, required_sib)) { + Error("SI Acquire is already running...\n"); + return proc_outcome_t::error; + } + search_state = search_state_t::si_acquire; + return proc_outcome_t::repeat; + } else { + // UE had SIB already. Handle its SIB + Info("Cell has SIB%d\n", required_sib + 1); + switch (required_sib) { + case 1: + rrc_ptr->handle_sib2(); + break; + case 12: + rrc_ptr->handle_sib13(); + break; + default: + break; + } + } + } + if (req_idx == required_sibs.size()) { + Info("Serving Cell Configuration Procedure has finished successfully\n"); + return proc_outcome_t::success; + } + } else if (search_state == search_state_t::si_acquire) { + uint32_t required_sib = required_sibs[req_idx]; + if (not rrc_ptr->si_acquirer.run()) { + si_acquire_proc ret = rrc_ptr->si_acquirer.pop(); + if (ret.is_error() or not rrc_ptr->serving_cell->has_sib(required_sib)) { + if (required_sib < 2) { + log_h->warning("Serving Cell Configuration has failed\n"); + return proc_outcome_t::error; + } + } + // continue with remaining SIBs + search_state = search_state_t::next_sib; + req_idx++; + return proc_outcome_t::repeat; + } + } + return proc_outcome_t::yield; +} + +/************************************** + * Cell Selection Procedure + *************************************/ + +/* + * Cell selection procedure 36.304 5.2.3 + * Select the best cell to camp on among the list of known cells + */ +proc_outcome_t rrc::cell_selection_proc::init(srsue::rrc* parent_) +{ + rrc_ptr = parent_; + log_h = parent_->rrc_log; + + if (rrc_ptr->neighbour_cells.empty() and rrc_ptr->serving_cell->in_sync and rrc_ptr->phy->cell_is_camping()) { + // don't bother with cell selection if there are no neighbours and we are already camping + return proc_outcome_t::success; + } + + Info("Starting a Cell Selection Procedure...\n"); + neigh_index = 0; + cs_result = cs_result_t::no_cell; + state = search_state_t::cell_selection; + return proc_outcome_t::repeat; +} + +proc_outcome_t rrc::cell_selection_proc::step_cell_selection() +{ + // Neighbour cells are sorted in descending order of RSRP + for (; neigh_index < rrc_ptr->neighbour_cells.size(); ++neigh_index) { + /*TODO: CHECK that PLMN matches. Currently we don't receive SIB1 of neighbour cells + * neighbour_cells[i]->plmn_equals(selected_plmn_id) && */ + if (rrc_ptr->neighbour_cells[neigh_index]->in_sync) { + // Matches S criteria + float rsrp = rrc_ptr->neighbour_cells[neigh_index]->get_rsrp(); + cell_t* serv_cell = rrc_ptr->serving_cell; + if (not serv_cell->in_sync or + (rrc_ptr->cell_selection_criteria(rsrp) and rsrp > rrc_ptr->serving_cell->get_rsrp() + 5)) { + // currently connected and verifies cell selection criteria + // Try to select Cell + rrc_ptr->set_serving_cell(neigh_index); + Info("Selected cell idx=%d, PCI=%d, EARFCN=%d\n", neigh_index, serv_cell->get_pci(), serv_cell->get_earfcn()); + log_h->console("Selected cell PCI=%d, EARFCN=%d\n", serv_cell->get_pci(), serv_cell->get_earfcn()); + + /* BLOCKING CALL */ + if (rrc_ptr->phy->cell_select(&serv_cell->phy_cell)) { + if (not rrc_ptr->serv_cell_cfg.launch(rrc_ptr, rrc_ptr->ue_required_sibs)) { + return proc_outcome_t::error; + } + state = search_state_t::cell_config; + return proc_outcome_t::repeat; + } else { + serv_cell->in_sync = false; + Error("Could not camp on serving cell.\n"); + // Continue to next neighbour cell + } + } + } + } + if (rrc_ptr->serving_cell->in_sync) { + if (not rrc_ptr->phy->cell_is_camping()) { + Info("Serving cell is in-sync but not camping. Selecting it...\n"); + + /* BLOCKING CALL */ + if (rrc_ptr->phy->cell_select(&rrc_ptr->serving_cell->phy_cell)) { + Info("Selected serving cell OK.\n"); + } else { + rrc_ptr->serving_cell->in_sync = false; + Error("Could not camp on serving cell.\n"); + } + } + cs_result = cs_result_t::same_cell; + return proc_outcome_t::success; + } + + // If can not find any suitable cell, search again + Info("Cell selection and reselection in IDLE did not find any suitable cell. Searching again\n"); + if (not rrc_ptr->cell_searcher.launch(rrc_ptr)) { + return proc_outcome_t::error; + } + state = search_state_t::cell_search; + return proc_outcome_t::repeat; +} + +proc_outcome_t rrc::cell_selection_proc::step_cell_search() +{ + + if (rrc_ptr->cell_searcher.run()) { + return proc_outcome_t::yield; + } + cell_search_proc ret = rrc_ptr->cell_searcher.pop(); + if (ret.is_error()) { + cs_result = cs_result_t::no_cell; + return proc_outcome_t::error; + } else { + cs_result = (ret.get_cs_ret().found == phy_interface_rrc_lte::cell_search_ret_t::CELL_FOUND) + ? cs_result_t::changed_cell + : cs_result_t::no_cell; + Info("Cell Search of cell selection run successfully\n"); + return proc_outcome_t::success; + } +} + +proc_outcome_t rrc::cell_selection_proc::step_cell_config() +{ + if (rrc_ptr->serv_cell_cfg.run()) { + return proc_outcome_t::yield; + } + serving_cell_config_proc ret = rrc_ptr->serv_cell_cfg.pop(); + if (ret.is_success()) { + Info("All SIBs of serving cell obtained successfully\n"); + cs_result = cs_result_t::changed_cell; + return proc_outcome_t::success; + } else { + Error("While configuring serving cell\n"); + // resume cell selection + state = search_state_t::cell_selection; + ++neigh_index; + return proc_outcome_t::repeat; + } +} + +proc_outcome_t rrc::cell_selection_proc::step() +{ + switch (state) { + case search_state_t::cell_selection: + return step_cell_selection(); + case search_state_t::cell_config: + return step_cell_config(); + case search_state_t::cell_search: + return step_cell_search(); + } + return proc_outcome_t::error; +} + +/************************************** + * PLMN search Procedure + *************************************/ + +proc_outcome_t rrc::plmn_search_proc::init(srsue::rrc* parent_) +{ + rrc_ptr = parent_; + log_h = parent_->rrc_log; + + Info("Starting PLMN search\n"); + nof_plmns = 0; + if (not rrc_ptr->cell_searcher.launch(rrc_ptr)) { + Error("Failed due to fail to init cell search...\n"); + return proc_outcome_t::error; + } + return proc_outcome_t::repeat; +} + +/* NAS interface to search for available PLMNs. + * It goes through all known frequencies, synchronizes and receives SIB1 for each to extract PLMN. + * The function is blocking and waits until all frequencies have been + * searched and PLMNs are obtained. + */ +proc_outcome_t rrc::plmn_search_proc::step() +{ + if (rrc_ptr->cell_searcher.run()) { + // wait for new TTI + return proc_outcome_t::yield; + } + cell_search_proc ret = rrc_ptr->cell_searcher.pop(); + phy_interface_rrc_lte::cell_search_ret_t cs_ret = ret.get_cs_ret(); + if (ret.is_error() or cs_ret.found == phy_interface_rrc_lte::cell_search_ret_t::ERROR) { + // stop search + nof_plmns = -1; + Error("Failed due to failed cell search sub-procedure\n"); + return proc_outcome_t::error; + } + + if (cs_ret.found == phy_interface_rrc_lte::cell_search_ret_t::CELL_FOUND) { + if (rrc_ptr->serving_cell->has_sib1()) { + // Save PLMN and TAC to NAS + for (uint32_t i = 0; i < rrc_ptr->serving_cell->nof_plmns(); i++) { + if (nof_plmns < MAX_FOUND_PLMNS) { + found_plmns[nof_plmns].plmn_id = rrc_ptr->serving_cell->get_plmn(i); + found_plmns[nof_plmns].tac = rrc_ptr->serving_cell->get_tac(); + nof_plmns++; + } else { + Error("No more space for plmns (%d)\n", nof_plmns); + } + } + } else { + Error("SIB1 not acquired\n"); + } + } + + if (cs_ret.last_freq == phy_interface_rrc_lte::cell_search_ret_t::NO_MORE_FREQS) { + Info("completed PLMN search\n"); + return proc_outcome_t::success; + } + + if (not rrc_ptr->cell_searcher.launch(rrc_ptr)) { + Error("Failed due to fail to init cell search...\n"); + return proc_outcome_t::error; + } + + // run again + return proc_outcome_t::repeat; +} + +void rrc::plmn_search_proc::stop() +{ + // on cleanup, call plmn_search_completed + if (is_success()) { + Info("completed with success\n"); + rrc_ptr->nas->plmn_search_completed(found_plmns, nof_plmns); + } else if (is_error()) { + Error("PLMN Search completed with an error\n"); + rrc_ptr->nas->plmn_search_completed(nullptr, -1); + } +} + +/************************************** + * Connection Request Procedure + *************************************/ + +proc_outcome_t rrc::connection_request_proc::init(rrc* parent_, + srslte::establishment_cause_t cause_, + srslte::unique_byte_buffer_t dedicated_info_nas_) +{ + rrc_ptr = parent_; + log_h = parent_->rrc_log; + cause = cause_; + dedicated_info_nas = std::move(dedicated_info_nas_); + + if (!rrc_ptr->plmn_is_selected) { + Error("Trying to connect but PLMN not selected.\n"); + return proc_outcome_t::error; + } + + if (rrc_ptr->state != RRC_STATE_IDLE) { + log_h->warning("Requested RRC connection establishment while not in IDLE\n"); + return proc_outcome_t::error; + } + + if (rrc_ptr->mac_timers->timer_get(rrc_ptr->t302)->is_running()) { + Info("Requested RRC connection establishment while T302 is running\n"); + rrc_ptr->nas->set_barring(nas_interface_rrc::BARRING_MO_DATA); + return proc_outcome_t::error; + } + + Info("Initiation of Connection establishment procedure\n"); + + cs_ret = cs_result_t::no_cell; + + if (not rrc_ptr->cell_selector.launch(rrc_ptr)) { + Error("Failed to initiate cell selection procedure...\n"); + return proc_outcome_t::error; + } + state = state_t::cell_selection; + return proc_outcome_t::repeat; +} + +proc_outcome_t rrc::connection_request_proc::step() +{ + if (state == state_t::cell_selection) { + // Perform cell selection & reselection for the selected PLMN + if (rrc_ptr->cell_selector.run()) { + return proc_outcome_t::yield; + } + cell_selection_proc result = rrc_ptr->cell_selector.pop(); + if (result.is_error()) { + return proc_outcome_t::error; + } + cs_ret = result.get_cs_result(); + + // .. and SI acquisition + if (rrc_ptr->phy->cell_is_camping()) { + + // Set default configurations + rrc_ptr->set_phy_default(); + rrc_ptr->set_mac_default(); + + // CCCH configuration applied already at start + // timeAlignmentCommon applied in configure_serving_cell + + Info("Configuring serving cell...\n"); + if (not rrc_ptr->serv_cell_cfg.launch(rrc_ptr, rrc_ptr->ue_required_sibs)) { + Error("Attach request failed to configure serving cell...\n"); + return proc_outcome_t::error; + } + state = state_t::config_serving_cell; + return proc_outcome_t::repeat; + } else { + switch (cs_ret) { + case cs_result_t::same_cell: + log_h->warning("Did not reselect cell but serving cell is out-of-sync.\n"); + rrc_ptr->serving_cell->in_sync = false; + break; + case cs_result_t::changed_cell: + log_h->warning("Selected a new cell but could not camp on. Setting out-of-sync.\n"); + rrc_ptr->serving_cell->in_sync = false; + break; + default: + log_h->warning("Could not find any suitable cell to connect\n"); + } + return proc_outcome_t::error; + } + } else if (state == state_t::config_serving_cell) { + if (rrc_ptr->serv_cell_cfg.run()) { + return proc_outcome_t::yield; + } + serving_cell_config_proc ret = rrc_ptr->serv_cell_cfg.pop(); + + if (ret.is_error()) { + Error("Configuring serving cell\n"); + return proc_outcome_t::error; + } + + rrc_ptr->mac_timers->timer_get(rrc_ptr->t300)->reset(); + rrc_ptr->mac_timers->timer_get(rrc_ptr->t300)->run(); + + // Send connectionRequest message to lower layers + rrc_ptr->send_con_request(cause); + + // Save dedicatedInfoNAS SDU + if (rrc_ptr->dedicated_info_nas.get()) { + log_h->warning("Received a new dedicatedInfoNAS SDU but there was one still in queue. Removing it\n"); + } + + Info("Waiting for RRCConnectionSetup/Reject or expiry\n"); + rrc_ptr->dedicated_info_nas = std::move(dedicated_info_nas); + state = state_t::wait_t300; + return proc_outcome_t::repeat; + + } else if (state == state_t::wait_t300) { + // Wait until t300 stops due to RRCConnectionSetup/Reject or expiry + if (rrc_ptr->mac_timers->timer_get(rrc_ptr->t300)->is_running()) { + return proc_outcome_t::yield; + } + + if (rrc_ptr->state == RRC_STATE_CONNECTED) { + // Received ConnectionSetup + return proc_outcome_t::success; + } else if (rrc_ptr->mac_timers->timer_get(rrc_ptr->t300)->is_expired()) { + // T300 is expired: 5.3.3.6 + Info("Timer T300 expired: ConnectionRequest timed out\n"); + rrc_ptr->mac->reset(); + rrc_ptr->set_mac_default(); + rrc_ptr->rlc->reestablish(); + } else { + // T300 is stopped but RRC not Connected is because received Reject: Section 5.3.3.8 + Info("Timer T300 stopped: Received ConnectionReject\n"); + rrc_ptr->mac->reset(); + rrc_ptr->set_mac_default(); + } + } + + return proc_outcome_t::error; +} + +void rrc::connection_request_proc::stop() +{ + if (is_error()) { + log_h->warning("Could not establish connection. Deallocating dedicatedInfoNAS PDU\n"); + this->dedicated_info_nas.reset(); + rrc_ptr->nas->connection_request_completed(false); + } else if (is_success()) { + Info("Finished connection request procedure successfully.\n"); + rrc_ptr->nas->connection_request_completed(true); + } +} + +/************************************** + * Process PCCH procedure + *************************************/ + +proc_outcome_t rrc::process_pcch_proc::init(rrc* parent_, const asn1::rrc::paging_s& paging_) +{ + rrc_ptr = parent_; + log_h = parent_->rrc_log; + paging = paging_; + + paging_idx = 0; + state = state_t::next_record; + Info("starting...\n"); + return proc_outcome_t::repeat; +} + +proc_outcome_t rrc::process_pcch_proc::step() +{ + if (state == state_t::next_record) { + for (; paging_idx < paging.paging_record_list.size(); ++paging_idx) { + srslte::s_tmsi_t s_tmsi_paged = srslte::make_s_tmsi_t(paging.paging_record_list[paging_idx].ue_id.s_tmsi()); + Info("Received paging (%d/%d) for UE %" PRIu64 ":%" PRIu64 "\n", + paging_idx + 1, + paging.paging_record_list.size(), + paging.paging_record_list[paging_idx].ue_id.s_tmsi().mmec.to_number(), + paging.paging_record_list[paging_idx].ue_id.s_tmsi().m_tmsi.to_number()); + if (rrc_ptr->ue_identity == s_tmsi_paged) { + if (RRC_STATE_IDLE == rrc_ptr->state) { + Info("S-TMS->I match in paging message\n"); + log_h->console("S-TMSI match in paging message\n"); + rrc_ptr->nas->paging(&s_tmsi_paged); + state = state_t::nas_paging; + return proc_outcome_t::repeat; + } else { + log_h->warning("Received paging while in CONNECT\n"); + } + } else { + Info("Received paging for unknown identity\n"); + } + } + if (paging.sys_info_mod_present) { + Info("Received System Information notification update request.\n"); + // invalidate and then update all SIBs of serving cell + rrc_ptr->serving_cell->reset_sibs(); + + // create a serving cell config procedure and push it to callback list + if (not rrc_ptr->serv_cell_cfg.launch(rrc_ptr, rrc_ptr->ue_required_sibs)) { + Error("Failed to initiate a serving cell configuration procedure\n"); + return proc_outcome_t::error; + } + } else { + Info("Completed successfully\n"); + return proc_outcome_t::success; + } + state = state_t::serv_cell_cfg; + return proc_outcome_t::repeat; + } else if (state == state_t::nas_paging) { + // wait for trigger + return proc_outcome_t::yield; + } else if (state == state_t::serv_cell_cfg) { + if (rrc_ptr->serv_cell_cfg.run()) { + return proc_outcome_t::yield; + } + serving_cell_config_proc ret = rrc_ptr->serv_cell_cfg.pop(); + if (ret.is_success()) { + Info("All SIBs of serving cell obtained successfully\n"); + return proc_outcome_t::success; + } else { + Error("While obtaining SIBs of serving cell\n"); + return proc_outcome_t::error; + } + } + return proc_outcome_t::yield; +} + +proc_outcome_t rrc::process_pcch_proc::trigger_event(paging_complete e) +{ + if (not e.outcome) { + Info("NAS Paging has failed\n"); + return proc_outcome_t::error; + } + paging_idx++; + state = state_t::next_record; + Info("Received paging complete event\n"); + return proc_outcome_t::repeat; +} + +/************************************** + * Go Idle procedure + *************************************/ + +proc_outcome_t rrc::go_idle_proc::init(rrc* rrc_) +{ + rrc_ptr = rrc_; + + rlc_flush_counter = 0; + Info("Starting...\n"); + return proc_outcome_t::yield; +} + +proc_outcome_t rrc::go_idle_proc::step() +{ + if (rrc_ptr->state == RRC_STATE_IDLE) { + Info("Interrupting as RRC has already became IDLE\n"); + return proc_outcome_t::success; + } + + // wait for max. 2s for RLC on SRB1 to be flushed + if (not rrc_ptr->rlc->has_data(RB_ID_SRB1) || ++rlc_flush_counter > rlc_flush_timeout) { + rrc_ptr->leave_connected(); + return proc_outcome_t::success; + } else { + rrc_ptr->rrc_log->debug( + "Postponing transition to RRC IDLE (%d ms < %d ms)\n", rlc_flush_counter, rlc_flush_timeout); + } + return proc_outcome_t::yield; +} + +} // namespace srsue diff --git a/srsue/src/stack/ue_stack_lte.cc b/srsue/src/stack/ue_stack_lte.cc index 98738aa44..fe5e95cad 100644 --- a/srsue/src/stack/ue_stack_lte.cc +++ b/srsue/src/stack/ue_stack_lte.cc @@ -157,7 +157,12 @@ void ue_stack_lte::stop_impl() bool ue_stack_lte::switch_on() { if (running) { - return nas.attach_request(); + proc_state_t proc_result = proc_state_t::on_going; + pending_tasks.push([this, &proc_result]() { nas.start_attach_request(&proc_result); }); + while (proc_result == proc_state_t::on_going) { + usleep(1000); + } + return proc_result == proc_state_t::success; } return false; @@ -221,6 +226,7 @@ void ue_stack_lte::run_tti_impl(uint32_t tti) { mac.run_tti(tti); rrc.run_tti(tti); + nas.run_tti(tti); } } // namespace srsue diff --git a/srsue/src/stack/upper/nas.cc b/srsue/src/stack/upper/nas.cc index 043a4519b..719ee00b9 100644 --- a/srsue/src/stack/upper/nas.cc +++ b/srsue/src/stack/upper/nas.cc @@ -36,8 +36,176 @@ using namespace srslte; +#define Error(fmt, ...) nas_ptr->nas_log->error("%s - " fmt, name(), ##__VA_ARGS__) +#define Warning(fmt, ...) nas_ptr->nas_log->warning("%s - " fmt, name(), ##__VA_ARGS__) +#define Info(fmt, ...) nas_ptr->nas_log->info("%s - " fmt, name(), ##__VA_ARGS__) + namespace srsue { +using srslte::proc_outcome_t; + +proc_outcome_t nas::plmn_search_proc::init(nas* nas_ptr_) +{ + nas_ptr = nas_ptr_; + + // start RRC + state = state_t::plmn_search; + if (not nas_ptr->rrc->plmn_search()) { + Error("Error while searching for PLMNs\n"); + return proc_outcome_t::error; + } + + Info("Starting...\n"); + return proc_outcome_t::yield; +} + +proc_outcome_t nas::plmn_search_proc::step() +{ + if (state == state_t::rrc_connect) { + if (nas_ptr->rrc_connector.run()) { + return proc_outcome_t::yield; + } + rrc_connect_proc ret = nas_ptr->rrc_connector.pop(); + if (ret.is_success()) { + return proc_outcome_t::success; + } + return proc_outcome_t::error; + } + return proc_outcome_t::yield; +} + +proc_outcome_t nas::plmn_search_proc::trigger_event(const plmn_search_complete_t& t) +{ + if (state != state_t::plmn_search) { + Warning("PLMN Search Complete was received but PLMN Search is not running.\n"); + return proc_outcome_t::yield; // ignore + } + + // check whether the state hasn't changed + if (nas_ptr->state != EMM_STATE_DEREGISTERED or nas_ptr->plmn_is_selected) { + Error("Error while searching for PLMNs\n"); + return proc_outcome_t::error; + } + + if (t.nof_plmns < 0) { + Error("Error while searching for PLMNs\n"); + return proc_outcome_t::error; + } else if (t.nof_plmns == 0) { + Warning("Did not find any PLMN in the set of frequencies.\n"); + return proc_outcome_t::error; + } + + // Save PLMNs + nas_ptr->known_plmns.clear(); + for (int i = 0; i < t.nof_plmns; i++) { + nas_ptr->known_plmns.push_back(t.found_plmns[i].plmn_id); + nas_ptr->nas_log->info( + "Found PLMN: Id=%s, TAC=%d\n", t.found_plmns[i].plmn_id.to_string().c_str(), t.found_plmns[i].tac); + nas_ptr->nas_log->console( + "Found PLMN: Id=%s, TAC=%d\n", t.found_plmns[i].plmn_id.to_string().c_str(), t.found_plmns[i].tac); + } + nas_ptr->select_plmn(); + + // Select PLMN in request establishment of RRC connection + if (not nas_ptr->plmn_is_selected) { + Error("PLMN is not selected because no suitable PLMN was found\n"); + return proc_outcome_t::error; + } + + nas_ptr->rrc->plmn_select(nas_ptr->current_plmn); + + if (not nas_ptr->rrc_connector.launch(nas_ptr)) { + Error("Unable to initiate RRC connection.\n"); + return proc_outcome_t::error; + } + + state = state_t::rrc_connect; + return proc_outcome_t::yield; +} + +proc_outcome_t nas::rrc_connect_proc::init(nas* nas_ptr_) +{ + nas_ptr = nas_ptr_; + + if (nas_ptr->rrc->is_connected()) { + Info("Stopping. Reason: Already connected\n"); + return proc_outcome_t::success; + } + + // Generate service request or attach request message + unique_byte_buffer_t dedicatedInfoNAS = srslte::allocate_unique_buffer(*nas_ptr->pool, true); + if (!dedicatedInfoNAS) { + Error("Fatal Error: Couldn't allocate PDU.\n"); + return proc_outcome_t::error; + } + + if (nas_ptr->state == EMM_STATE_REGISTERED) { + nas_ptr->gen_service_request(dedicatedInfoNAS.get()); + } else { + nas_ptr->gen_attach_request(dedicatedInfoNAS.get()); + } + + // Provide UE-Identity to RRC if have one + if (nas_ptr->have_guti) { + srslte::s_tmsi_t s_tmsi; + s_tmsi.mmec = nas_ptr->ctxt.guti.mme_code; + s_tmsi.m_tmsi = nas_ptr->ctxt.guti.m_tmsi; + nas_ptr->rrc->set_ue_identity(s_tmsi); + } + + establishment_cause_t establish_cause = establishment_cause_t::mo_sig; + if (nas_ptr->state == EMM_STATE_REGISTERED) { + // FIXME: only need to use MT_ACCESS for establishment after paging + establish_cause = establishment_cause_t::mt_access; + } + + state = state_t::conn_req; + if (not nas_ptr->start_connection_request(establish_cause, std::move(dedicatedInfoNAS))) { + return proc_outcome_t::error; + } + + Info("Starting...\n"); + return proc_outcome_t::yield; +} + +proc_outcome_t nas::rrc_connect_proc::step() +{ + if (state == state_t::conn_req) { + if (nas_ptr->conn_req_proc.run()) { + return proc_outcome_t::yield; + } + query_proc_t ret = nas_ptr->conn_req_proc.pop(); + if (not ret.result()) { + Error("Could not establish RRC connection\n"); + return proc_outcome_t::error; + } + Info("Connection established correctly. Waiting for Attach\n"); + wait_timeout = 0; + // Wait until attachment. If doing a service request is already attached + state = state_t::wait_attach; + return proc_outcome_t::repeat; + } else if (state == state_t::wait_attach) { + wait_timeout++; + // Wait until attachment. If doing a service request is already attached + if (wait_timeout >= 5000 or nas_ptr->state == EMM_STATE_REGISTERED or not nas_ptr->running or + not nas_ptr->rrc->is_connected()) { + if (nas_ptr->state == EMM_STATE_REGISTERED) { + Info("EMM Registered correctly\n"); + return proc_outcome_t::success; + } else if (nas_ptr->state == EMM_STATE_DEREGISTERED) { + Error("Timeout or received attach reject while trying to attach\n"); + nas_ptr->nas_log->console("Failed to Attach\n"); + } else if (!nas_ptr->rrc->is_connected()) { + Error("Was disconnected while attaching\n"); + } else { + Error("Timed out while trying to attach\n"); + } + return proc_outcome_t::error; + } + } + return proc_outcome_t::yield; +} + /********************************************************************* * NAS ********************************************************************/ @@ -113,6 +281,11 @@ emm_state_t nas::get_state() { return state; } +void nas::run_tti(uint32_t tti) +{ + callbacks.run(); +} + /******************************************************************************* * UE interface ******************************************************************************/ @@ -121,9 +294,8 @@ emm_state_t nas::get_state() { * The function returns true if the UE could attach correctly or false in case of error or timeout during attachment. * */ -bool nas::attach_request() { - rrc_interface_nas::found_plmn_t found_plmns[rrc_interface_nas::MAX_FOUND_PLMNS]; - int nof_plmns = 0; +void nas::start_attach_request(srslte::proc_state_t* result) +{ nas_log->info("Attach Request\n"); switch (state) { @@ -132,57 +304,59 @@ bool nas::attach_request() { // Search PLMN is not selected if (!plmn_is_selected) { nas_log->info("No PLMN selected. Starting PLMN Search...\n"); - nof_plmns = rrc->plmn_search(found_plmns); - if (nof_plmns > 0) { - // Save PLMNs - known_plmns.clear(); - for (int i=0;iinfo( - "Found PLMN: Id=%s, TAC=%d\n", found_plmns[i].plmn_id.to_string().c_str(), found_plmns[i].tac); - nas_log->console( - "Found PLMN: Id=%s, TAC=%d\n", found_plmns[i].plmn_id.to_string().c_str(), found_plmns[i].tac); - } - select_plmn(); - } else if (nof_plmns == 0) { - nas_log->warning("Did not find any PLMN in the set of frequencies\n"); - return false; - } else if (nof_plmns < 0) { - nas_log->error("Error while searching for PLMNs\n"); - return false; - } - } - // Select PLMN in request establishment of RRC connection - if (plmn_is_selected) { - rrc->plmn_select(current_plmn); - if (rrc_connect()) { - nas_log->info("NAS attached successfully.\n"); - return true; - } else { - nas_log->error("Could not attach in attach request\n"); + if (not plmn_searcher.launch(this)) { + *result = proc_state_t::error; + return; } + callbacks.defer_task([this, result]() { + if (plmn_searcher.run()) { + return proc_outcome_t::yield; + } + plmn_search_proc p = plmn_searcher.pop(); + nas_log->info("Attach Request from PLMN Search %s\n", p.is_success() ? "finished successfully" : "failed"); + *result = p.is_success() ? proc_state_t::success : proc_state_t::error; + return proc_outcome_t::success; + }); } else { - nas_log->error("PLMN is not selected because no suitable PLMN was found\n"); + *result = proc_state_t::error; } break; case EMM_STATE_REGISTERED: if (rrc->is_connected()) { nas_log->info("NAS is already registered and RRC connected\n"); - return true; + *result = proc_state_t::success; } else { nas_log->info("NAS is already registered but RRC disconnected. Connecting now...\n"); - if (rrc_connect()) { - nas_log->info("NAS attached successfully.\n"); - return true; - } else { - nas_log->error("Could not attach from attach_request\n"); + if (not rrc_connector.launch(this)) { + nas_log->error("Cannot initiate concurrent rrc connection procedures\n"); + *result = proc_state_t::error; + return; } + callbacks.defer_task([this, result]() { + if (rrc_connector.run()) { + return proc_outcome_t::yield; + } + rrc_connect_proc proc = rrc_connector.pop(); + if (proc.is_success()) { + nas_log->info("NAS attached successfully.\n"); + } else { + nas_log->error("Could not attach from attach_request\n"); + } + *result = proc.is_success() ? proc_state_t::success : proc_state_t::error; + return proc_outcome_t::success; + }); } break; default: nas_log->info("Attach request ignored. State = %s\n", emm_state_text[state]); + *result = proc_state_t::error; } - return false; +} + +void nas::plmn_search_completed(rrc_interface_nas::found_plmn_t found_plmns[rrc_interface_nas::MAX_FOUND_PLMNS], + int nof_plmns) +{ + plmn_searcher.trigger_event(plmn_search_proc::plmn_search_complete_t(found_plmns, nof_plmns)); } bool nas::detach_request() { @@ -220,11 +394,24 @@ void nas::paging(s_tmsi_t* ue_identity) { if (state == EMM_STATE_REGISTERED) { nas_log->info("Received paging: requesting RRC connection establishment\n"); - if (rrc_connect()) { - nas_log->info("Attached successfully\n"); - } else { - nas_log->error("Could not attach from paging\n"); + if (rrc_connector.is_active()) { + nas_log->error("Cannot initiate concurrent RRC connection establishment procedures\n"); + return; + } + if (not rrc_connector.launch(this)) { + nas_log->error("Could not launch RRC Connect()\n"); + return; } + // once completed, call paging complete + callbacks.defer_task([this]() { + if (rrc_connector.run()) { + return proc_outcome_t::yield; + } + bool success = rrc_connector.pop().is_success(); + rrc->paging_completed(success); + return proc_outcome_t::success; + }); + } else { nas_log->warning("Received paging while in state %s\n", emm_state_text[state]); } @@ -234,66 +421,25 @@ void nas::set_barring(barring_t barring) { current_barring = barring; } -/* Internal function that requests RRC connection, waits for positive or negative response and returns true/false - */ -bool nas::rrc_connect() { - if (rrc->is_connected()) { - nas_log->info("Already connected\n"); - return true; - } - - // Generate service request or attach request message - unique_byte_buffer_t dedicatedInfoNAS = srslte::allocate_unique_buffer(*pool, true); - if (!dedicatedInfoNAS) { - nas_log->error("Fatal Error: Couldn't allocate PDU in rrc_connect().\n"); +bool nas::start_connection_request(srslte::establishment_cause_t establish_cause, + srslte::unique_byte_buffer_t ded_info_nas) +{ + if (not conn_req_proc.launch()) { + nas_log->error("Failed to initiate a connection request procedure\n"); return false; } - - if (state == EMM_STATE_REGISTERED) { - gen_service_request(dedicatedInfoNAS.get()); - } else { - gen_attach_request(dedicatedInfoNAS.get()); - } - - // Provide UE-Identity to RRC if have one - if (have_guti) { - s_tmsi_t s_tmsi; - s_tmsi.m_tmsi = ctxt.guti.m_tmsi; - s_tmsi.mmec = ctxt.guti.mme_code; - rrc->set_ue_identity(s_tmsi); - } - - // Set establishment cause - srslte::establishment_cause_t establish_cause = srslte::establishment_cause_t::mo_sig; - if (state == EMM_STATE_REGISTERED) { - // FIXME: only need to use MT_ACCESS for establishment after paging - establish_cause = establishment_cause_t::mt_access; + if (not rrc->connection_request(establish_cause, std::move(ded_info_nas))) { + nas_log->error("Failed to initiate a connection request procedure\n"); + conn_req_proc.pop(); + return false; } + return true; +} - if (rrc->connection_request(establish_cause, std::move(dedicatedInfoNAS))) { - nas_log->info("Connection established correctly. Waiting for Attach\n"); - - // Wait until attachment. If doing a service request is already attached - uint32_t tout = 0; - while (tout < 5000 && state != EMM_STATE_REGISTERED && running && rrc->is_connected()) { - usleep(1000); - tout++; - } - if (state == EMM_STATE_REGISTERED) { - nas_log->info("EMM Registered correctly\n"); - return true; - } else if (state == EMM_STATE_DEREGISTERED) { - nas_log->error("Timeout or received attach reject while trying to attach\n"); - nas_log->console("Failed to Attach\n"); - } else if (!rrc->is_connected()) { - nas_log->error("Was disconnected while attaching\n"); - } else { - nas_log->error("Timed out while trying to attach\n"); - } - } else { - nas_log->error("Could not establish RRC connection\n"); - } - return false; +bool nas::connection_request_completed(bool outcome) +{ + conn_req_proc.trigger_event(outcome); + return conn_req_proc.is_active(); } void nas::select_plmn() { @@ -311,7 +457,7 @@ void nas::select_plmn() { } // If not, select the first available PLMN - if (known_plmns.size() > 0) { + if (not known_plmns.empty()) { nas_log->info("Could not find Home PLMN Id=%s, trying to connect to PLMN Id=%s\n", home_plmn.to_string().c_str(), known_plmns[0].to_string().c_str()); @@ -359,7 +505,7 @@ void nas::write_pdu(uint32_t lcid, unique_byte_buffer_t pdu) } // Write NAS pcap - if(pcap != NULL) { + if (pcap != nullptr) { pcap->write_nas(pdu->msg, pdu->N_bytes); } @@ -424,7 +570,6 @@ void nas::set_k_enb_count(uint32_t count) { // UL count for RRC key derivation depends on UL Count of the Attach Request or Service Request. // On the case of an Authentication Request, the UL count used to generate K_enb must be reset to zero. ctxt.k_enb_count = count; - return; } uint32_t nas::get_k_enb_count() { @@ -436,7 +581,7 @@ bool nas::get_k_asme(uint8_t *k_asme_, uint32_t n) { nas_log->error("K_asme requested before security context established\n"); return false; } - if(NULL == k_asme_ || n < 32) { + if (nullptr == k_asme_ || n < 32) { nas_log->error("Invalid parameters to get_k_asme"); return false; } @@ -1007,7 +1152,7 @@ void nas::parse_security_mode_command(uint32_t lcid, unique_byte_buffer_t pdu) nas_log->debug("Generating integrity check. integ_algo:%d, count_dl:%d, lcid:%d\n", ctxt.integ_algo, ctxt.rx_count, lcid); - if (integrity_check(pdu.get()) != true) { + if (not integrity_check(pdu.get())) { nas_log->warning("Sending Security Mode Reject due to integrity check failure\n"); send_security_mode_reject(LIBLTE_MME_EMM_CAUSE_MAC_FAILURE); return; @@ -1516,7 +1661,9 @@ void nas::send_detach_request(bool switch_off) if (rrc->is_connected()) { rrc->write_sdu(std::move(pdu)); } else { - rrc->connection_request(establishment_cause_t::mo_sig, std::move(pdu)); + if (not start_connection_request(establishment_cause_t::mo_sig, std::move(pdu))) { + nas_log->info("Failed to initiate RRC Connection Request\n"); + } } } diff --git a/srsue/test/upper/nas_test.cc b/srsue/test/upper/nas_test.cc index 9cfc2601d..a46369296 100644 --- a/srsue/test/upper/nas_test.cc +++ b/srsue/test/upper/nas_test.cc @@ -100,6 +100,7 @@ public: plmns.plmn_id.from_number(mcc, mnc); plmns.tac = 0xffff; } + void init(nas* nas_) { nas_ptr = nas_; } void write_sdu(unique_byte_buffer_t sdu) { last_sdu_len = sdu->N_bytes; @@ -110,10 +111,11 @@ public: uint32_t get_last_sdu_len() { return last_sdu_len; } void reset() { last_sdu_len = 0; } - int plmn_search(srsue::rrc_interface_nas::found_plmn_t* found) { - memcpy(found, &plmns, sizeof(found_plmn_t)); - return 1; - }; + bool plmn_search() + { + nas_ptr->plmn_search_completed(&plmns, 1); + return true; + } void plmn_select(srslte::plmn_id_t plmn_id){}; void set_ue_identity(srslte::s_tmsi_t s_tmsi) {} bool connection_request(srslte::establishment_cause_t cause, srslte::unique_byte_buffer_t sdu) @@ -121,6 +123,7 @@ public: printf("NAS generated SDU (len=%d):\n", sdu->N_bytes); last_sdu_len = sdu->N_bytes; srslte_vec_fprint_byte(stdout, sdu->msg, sdu->N_bytes); + nas_ptr->connection_request_completed(true); return true; } bool is_connected() {return false;} @@ -129,8 +132,10 @@ public: uint16_t get_mnc() { return mnc; } void enable_capabilities() {} uint32_t get_lcid_for_eps_bearer(const uint32_t& eps_bearer_id) { return 0; } + void paging_completed(bool outcome) {} private: + nas* nas_ptr; uint32_t last_sdu_len; found_plmn_t plmns; }; @@ -140,13 +145,28 @@ class stack_dummy : public stack_interface_gw, public thread public: stack_dummy(pdcp_interface_gw* pdcp_, srsue::nas* nas_) : pdcp(pdcp_), nas(nas_), thread("DUMMY STACK") {} void init() { start(-1); } - bool switch_on() final { return nas->attach_request(); } + bool switch_on() final + { + proc_state_t proc_result = proc_state_t::on_going; + nas->start_attach_request(&proc_result); + while (proc_result == proc_state_t::on_going) { + usleep(1000); + } + return proc_result == proc_state_t::success; + } void write_sdu(uint32_t lcid, srslte::unique_byte_buffer_t sdu, bool blocking) { pdcp->write_sdu(lcid, std::move(sdu), blocking); } bool is_lcid_enabled(uint32_t lcid) { return pdcp->is_lcid_enabled(lcid); } - void run_thread() { running = true; } + void run_thread() + { + running = true; + uint32_t counter = 0; + while (running) { + nas->run_tti(counter++); + } + } void stop() { running = false; @@ -211,6 +231,7 @@ int security_command_test() cfg.eia = "1,2"; cfg.eea = "0,1,2"; nas.init(&usim, &rrc_dummy, &gw, cfg); + rrc_dummy.init(&nas); // push auth request PDU to NAS to generate security context byte_buffer_pool* pool = byte_buffer_pool::get_instance(); @@ -279,6 +300,7 @@ int mme_attach_request_test() stack_dummy stack(&pdcp_dummy, &nas); nas.init(&usim, &rrc_dummy, &gw, nas_cfg); + rrc_dummy.init(&nas); gw_args_t gw_args; gw_args.tun_dev_name = "tun0";