From 7cbedb15756e398137c825199cd988c7e3054a86 Mon Sep 17 00:00:00 2001 From: Cedric Roux Date: Thu, 10 Oct 2024 17:48:01 +0200 Subject: [PATCH] time management module This commit introduces a time management module. See doc/time_management.md for details. --- CMakeLists.txt | 5 +- common/utils/CMakeLists.txt | 1 + common/utils/time_manager/CMakeLists.txt | 14 + .../utils/time_manager/tests/CMakeLists.txt | 11 + common/utils/time_manager/tests/test_auto.c | 285 ++++++++++++++++++ common/utils/time_manager/tests/test_manual.c | 175 +++++++++++ common/utils/time_manager/time_client.c | 231 ++++++++++++++ common/utils/time_manager/time_client.h | 36 +++ common/utils/time_manager/time_manager.c | 240 +++++++++++++++ common/utils/time_manager/time_manager.h | 46 +++ common/utils/time_manager/time_server.c | 251 +++++++++++++++ common/utils/time_manager/time_server.h | 40 +++ common/utils/time_manager/time_source.c | 264 ++++++++++++++++ common/utils/time_manager/time_source.h | 47 +++ doc/SW_archi.md | 2 +- doc/time_management.md | 284 +++++++++++++++++ executables/nr-cuup.c | 8 + executables/nr-ru.c | 5 + executables/nr-softmodem.c | 15 + executables/nr-ue.c | 15 +- executables/nr-uesoftmodem.c | 12 + openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c | 10 - openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 19 -- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h | 2 - .../LAYER2/nr_pdcp/nr_pdcp_timer_thread.c | 24 +- .../LAYER2/nr_pdcp/nr_pdcp_timer_thread.h | 3 +- openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c | 11 +- openair2/X2AP/x2ap_eNB.c | 2 +- openair2/X2AP/x2ap_eNB.h | 1 - openair2/X2AP/x2ap_timers.c | 3 +- 30 files changed, 1983 insertions(+), 79 deletions(-) create mode 100644 common/utils/time_manager/CMakeLists.txt create mode 100644 common/utils/time_manager/tests/CMakeLists.txt create mode 100644 common/utils/time_manager/tests/test_auto.c create mode 100644 common/utils/time_manager/tests/test_manual.c create mode 100644 common/utils/time_manager/time_client.c create mode 100644 common/utils/time_manager/time_client.h create mode 100644 common/utils/time_manager/time_manager.c create mode 100644 common/utils/time_manager/time_manager.h create mode 100644 common/utils/time_manager/time_server.c create mode 100644 common/utils/time_manager/time_server.h create mode 100644 common/utils/time_manager/time_source.c create mode 100644 common/utils/time_manager/time_source.h create mode 100644 doc/time_management.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 25bc56b79a..3a567fc3c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1835,6 +1835,7 @@ target_link_libraries(nr-softmodem PRIVATE ITTI ${NAS_UE_LIB} lte_rrc nr_rrc ngap s1ap L2_LTE_NR L2_NR MAC_NR_COMMON NFAPI_LIB NFAPI_VNF_LIB NFAPI_PNF_LIB NFAPI_USER_LIB SIMU x2ap f1ap m2ap m3ap e1ap shlib_loader + time_management -Wl,--end-group z dl) target_link_libraries(nr-softmodem PRIVATE pthread m CONFIG_LIB rt sctp) @@ -1867,6 +1868,7 @@ add_executable(nr-cuup target_link_libraries(nr-cuup PRIVATE CONFIG_LIB ITTI SCTP_CLIENT GTPV1U e1ap f1ap + time_management z sctp dl pthread shlib_loader ${T_LIB}) target_link_libraries(nr-cuup PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) if(E2_AGENT) @@ -1898,6 +1900,7 @@ target_link_libraries(nr-uesoftmodem PRIVATE nr_rrc SECURITY UTIL HASHTABLE SCHED_RU_LIB SCHED_NR_UE_LIB PHY_COMMON PHY_NR_COMMON PHY_NR_UE NR_L2_UE MAC_NR_COMMON NFAPI_LIB ITTI SIMU shlib_loader + time_management -Wl,--end-group z dl) target_link_libraries(nr-uesoftmodem PRIVATE pthread m CONFIG_LIB rt nr_ue_phy_meas) @@ -1993,7 +1996,7 @@ add_executable(nr_psbchsim ${PHY_INTERFACE_DIR}/queue_t.c ) target_link_libraries(nr_psbchsim PRIVATE - -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON CONFIG_LIB L2_NR -lz -Wl,--end-group + -Wl,--start-group ngap UTIL SIMU PHY_COMMON PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON CONFIG_LIB L2_NR -lz -Wl,--end-group m pthread ${T_LIB} ITTI dl shlib_loader physim_common ) target_link_libraries(nr_psbchsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) diff --git a/common/utils/CMakeLists.txt b/common/utils/CMakeLists.txt index b311d0542f..c3c7720628 100644 --- a/common/utils/CMakeLists.txt +++ b/common/utils/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory(T) add_subdirectory(nr) add_subdirectory(LOG) add_subdirectory(threadPool) +add_subdirectory(time_manager) add_library(utils utils.c system.c time_meas.c time_stat.c tun_if.c) target_include_directories(utils PUBLIC .) target_link_libraries(utils PRIVATE ${T_LIB}) diff --git a/common/utils/time_manager/CMakeLists.txt b/common/utils/time_manager/CMakeLists.txt new file mode 100644 index 0000000000..3a197ae556 --- /dev/null +++ b/common/utils/time_manager/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(time_management STATIC + time_manager.c) + +add_library(time_management_core OBJECT + time_source.c + time_server.c + time_client.c) + +target_link_libraries(time_management PUBLIC time_management_core) +target_link_libraries(time_management_core PRIVATE ${T_LIB}) + +if(ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/common/utils/time_manager/tests/CMakeLists.txt b/common/utils/time_manager/tests/CMakeLists.txt new file mode 100644 index 0000000000..b3b57a67d4 --- /dev/null +++ b/common/utils/time_manager/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(test_time_manager_manual test_manual.c) +target_link_libraries(test_time_manager_manual time_management_core) +target_link_libraries(test_time_manager_manual LOG CONFIG_LIB) + +add_executable(test_time_manager_auto test_auto.c) +target_link_libraries(test_time_manager_auto time_management) +target_link_libraries(test_time_manager_auto LOG CONFIG_LIB) + +add_dependencies(tests test_time_manager_auto) +add_test(NAME time_management_tests + COMMAND ./test_time_manager_auto) diff --git a/common/utils/time_manager/tests/test_auto.c b/common/utils/time_manager/tests/test_auto.c new file mode 100644 index 0000000000..73bf27af88 --- /dev/null +++ b/common/utils/time_manager/tests/test_auto.c @@ -0,0 +1,285 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include +#include +#include +#include + +#include "common/utils/time_manager/time_manager.h" +#include "common/utils/LOG/log.h" +#include "common/config/config_userapi.h" + +configmodule_interface_t *uniqCfg = NULL; + +void exit_function(const char *file, const char *function, const int line, const char *s, const int assert) +{ + if (s != NULL) { + printf("%s:%d %s() Exiting OAI softmodem: %s\n", file, line, function, s); + } + if (assert) + abort(); + else + exit(1); +} + +int is_x2ap_enabled(void) +{ + /* we want x2ap ticks */ + return 1; +} + +static int rlc_tick_count = 0; +static int pdcp_tick_count = 0; +static int x2ap_tick_count = 0; + +void nr_rlc_ms_tick(void) +{ + rlc_tick_count++; +} + +void nr_pdcp_ms_tick(void) +{ + pdcp_tick_count++; +} + +void x2ap_ms_tick(void) +{ + x2ap_tick_count++; +} + +/* return 1 if ok, 0 if error */ +static int standalone_realtime(void) +{ + time_manager_start(TIME_MANAGER_GNB_MONOLITHIC, TIME_MANAGER_REALTIME); + + sleep(1); + + time_manager_finish(); + + LOG_I(UTIL, + "standalone realtime: rlc ticks: %d pdcp ticks: %d x2ap ticks: %d\n", + rlc_tick_count, + pdcp_tick_count, + x2ap_tick_count); + + /* let's accept 1% deviation from the 1000 counts we expect */ + return abs(rlc_tick_count-1000) < 10 + && abs(pdcp_tick_count - 1000) < 10 + && abs(x2ap_tick_count - 1000) < 10; +} + +/* return 1 if ok, 0 if error */ +static int standalone_iq_samples(void) +{ + time_manager_start(TIME_MANAGER_GNB_MONOLITHIC, TIME_MANAGER_IQ_SAMPLES); + + /* let's pretend we have 2000 samples per second */ + for (int i = 0; i < 1000; i++) { + time_manager_iq_samples(1, 2000); + usleep(10); + time_manager_iq_samples(1, 2000); + usleep(10); + } + + time_manager_finish(); + + LOG_I(UTIL, + "standalone iq-samples: rlc ticks: %d pdcp ticks: %d x2ap ticks: %d\n", + rlc_tick_count, + pdcp_tick_count, + x2ap_tick_count); + + /* exact count is required */ + return rlc_tick_count == 1000 + && pdcp_tick_count == 1000 + && x2ap_tick_count == 1000; +} + +/* return 1 if ok, 0 if error */ +static int client_server(char *program_name, bool iq_samples_time_source) +{ + LOG_I(UTIL, "client/server %s: launch sub-processes\n", iq_samples_time_source ? "iq-samples" : "realtime"); + + pid_t server; + pid_t client; + + /* start server */ + server = fork(); + DevAssert(server >= 0); + if (server == 0) { + /* run as server */ + if (iq_samples_time_source) + execl(program_name, program_name, "--time_management.mode", "server", "--time_management.time_source", "iq_samples", NULL); + else + execl(program_name, program_name, "--time_management.mode", "server", NULL); + exit(1); + } + + /* wait a little bit so that server is ready to accept clients + * (this is hackish) + */ + usleep(100); + + /* start client */ + client = fork(); + DevAssert(client >= 0); + if (client == 0) { + /* run as client */ + execl(program_name, program_name, "--time_management.mode", "client", NULL); + exit(1); + } + + /* wait for completion */ + int ret = 1; + + int status; + pid_t r = waitpid(server, &status, 0); + DevAssert(r == server); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + ret = 0; + + r = waitpid(client, &status, 0); + DevAssert(r == client); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + ret = 0; + + return ret; +} + +/* return 1 if ok, 0 if error */ +static int run_sub_client_server(bool iq_samples_time_source) +{ + /* second argument doesn't matter, overwritten by args of execl in client_server() */ + time_manager_start(TIME_MANAGER_GNB_MONOLITHIC, TIME_MANAGER_REALTIME); + + if (iq_samples_time_source) { + /* 'iq_samples_time_source' is true only for server + * wait a bit for client to be there otherwise server + * may finish too early + */ + usleep(100); + + /* let's pretend we have 2000 samples per second */ + for (int i = 0; i < 1000; i++) { + time_manager_iq_samples(1, 2000); + usleep(10); + time_manager_iq_samples(1, 2000); + usleep(10); + } + } else { + sleep(1); + } + + time_manager_finish(); + + LOG_I(UTIL, "client/server: rlc ticks: %d pdcp ticks: %d x2ap ticks: %d\n", rlc_tick_count, pdcp_tick_count, x2ap_tick_count); + + if (iq_samples_time_source) + return rlc_tick_count == 1000 + && pdcp_tick_count == 1000 + && x2ap_tick_count == 1000; + else + /* let's accept 1% deviation from the 1000 counts we expect */ + return abs(rlc_tick_count-1000) < 10 + && abs(pdcp_tick_count - 1000) < 10 + && abs(x2ap_tick_count - 1000) < 10; +} + +int main(int argc, char **argv) +{ + if ((uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY)) == NULL) { + exit_fun("[SOFTMODEM] Error, configuration module init failed\n"); + } + logInit(); + + /* for client/server tests, we use sub-processes with command line parameters */ + if (argc > 1) { + /* if there are command line parameters then it's a sub-process */ + rlc_tick_count = 0; + pdcp_tick_count = 0; + x2ap_tick_count = 0; + /* return value to the main test program + * let's do as for the shell: 0 means success and 1 means error + */ + /* we know we are in iq-samples time source mode if the last argument is "iq_samples" */ + int success = run_sub_client_server(!strcmp(argv[argc - 1], "iq_samples")) == 1; + return 1 - success; + } + + int err = 0; + + /* test standalone realtime */ + rlc_tick_count = 0; + pdcp_tick_count = 0; + x2ap_tick_count = 0; + int ret = standalone_realtime(); + if (ret == 1) + LOG_I(UTIL, "standalone realtime: OK\n"); + else { + LOG_E(UTIL, "standalone realtime: ERROR\n"); + err = 1; + } + + /* test standalone iq-samples */ + rlc_tick_count = 0; + pdcp_tick_count = 0; + x2ap_tick_count = 0; + ret = standalone_iq_samples(); + if (ret == 1) + LOG_I(UTIL, "standalone iq-samples: OK\n"); + else { + LOG_E(UTIL, "standalone iq-samples: ERROR\n"); + err = 1; + } + + /* client/server tests are not written in a robust way + * they may fail depending on realtime on the machine + * de-activate if needed (if CI fails) + */ + /* test client/server realtime */ + rlc_tick_count = 0; + pdcp_tick_count = 0; + x2ap_tick_count = 0; + ret = client_server(argv[0], false); + if (ret == 1) + LOG_I(UTIL, "client-server realtime: OK\n"); + else { + LOG_E(UTIL, "client-server realtime: ERROR\n"); + err = 1; + } + + /* test client/server iq-samples */ + rlc_tick_count = 0; + pdcp_tick_count = 0; + x2ap_tick_count = 0; + ret = client_server(argv[0], true); + if (ret == 1) + LOG_I(UTIL, "client-server iq-samples: OK\n"); + else { + LOG_E(UTIL, "client-server iq-samples: ERROR\n"); + err = 1; + } + + logTerm(); + + return err; +} diff --git a/common/utils/time_manager/tests/test_manual.c b/common/utils/time_manager/tests/test_manual.c new file mode 100644 index 0000000000..e0cc11873a --- /dev/null +++ b/common/utils/time_manager/tests/test_manual.c @@ -0,0 +1,175 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include "../time_source.h" +#include "../time_server.h" +#include "../time_client.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "common/utils/system.h" +#include "common/utils/LOG/log.h" + +#include "common/config/config_userapi.h" + +configmodule_interface_t *uniqCfg = NULL; + +void exit_function(const char *file, const char *function, const int line, const char *s, const int assert) +{ + printf("%s:%d:%s: %s\n", file, line, function, s); + fflush(stdout); + exit(1); +} + +_Atomic bool test_exit; + +static void *iq_generate_thread(void *ts) +{ + time_source_t *time_source = ts; + + while (!test_exit) { + printf("iq_generate_thread calls time_source_iq_add(time_source, 100, 10000)\n"); + time_source_iq_add(time_source, 100, 10000); + sleep(1); + } + + return NULL; +} + +#define STANDALONE 0 +#define SERVER 1 +#define CLIENT 2 + +static void usage(void) +{ + printf("options:\n"); + printf(" -client\n"); + printf(" run as client (default is standalone)\n"); + printf(" -server\n"); + printf(" run as server (default is standalone)\n"); + printf(" -ip \n"); + printf(" use this ip address for server\n"); + printf(" -port n-2) usage(); server_ip = v[++i]; continue; } + if (!strcmp(v[i], "-port")) { if (i>n-2) usage(); server_port = atoi(v[++i]); continue; } + if (!strcmp(v[i], "-realtime")) { time_source_type = TIME_SOURCE_REALTIME; continue; } + usage(); + } + + logInit(); + /* hack: set log to info (logInit doesn't do it because we don't call load_configmodule()) */ + for (int i = 0; i < MAX_LOG_COMPONENTS; i++) + g_log->log_component[i].level = OAILOG_INFO; + + if (mode != CLIENT) { + ts = new_time_source(time_source_type); + if (time_source_type == TIME_SOURCE_IQ_SAMPLES) { + void *ret = signal(SIGHUP, sig_handle); + DevAssert(ret != SIG_ERR); + threadCreate(&iq_thread, iq_generate_thread, ts, "iq samples generator", -1, SCHED_OAI); + } + } + + if (mode == SERVER) { + /* (void*)1 to check if callback data is passed correctly */ + server = new_time_server(server_ip, server_port, server_callback, (void *)1); + time_server_attach_time_source(server, ts); + } + + if (mode == CLIENT) { + /* (void*)2 to check if callback data is passed correctly */ + client = new_time_client(server_ip, server_port, client_callback, (void *)2); + } + + printf("main: press enter to quit\n"); + getchar(); + + test_exit = true; + + if (mode != CLIENT) + if (time_source_type == TIME_SOURCE_IQ_SAMPLES) { + (void)pthread_kill(iq_thread, SIGHUP); + int ret = pthread_join(iq_thread, NULL); + DevAssert(ret == 0); + } + + if (mode != CLIENT) { + free_time_source(ts); + } + + if (mode == SERVER) { + free_time_server(server); + } + + if (mode == CLIENT) { + free_time_client(client); + } + + logTerm(); + + return 0; +} diff --git a/common/utils/time_manager/time_client.c b/common/utils/time_manager/time_client.c new file mode 100644 index 0000000000..4cecf0e6c6 --- /dev/null +++ b/common/utils/time_manager/time_client.c @@ -0,0 +1,231 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include "time_client.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/utils/assertions.h" +#include "common/utils/LOG/log.h" +#include "common/utils/system.h" + +typedef struct { + pthread_t thread_id; + int exit_fd; + struct sockaddr_in server_ip; + int server_port; + void (*callback)(void *); + void *callback_data; +} time_client_private_t; + +static int connect_to_server(time_client_private_t *time_client) +{ + int sock = -1; + int opts; + int r; + struct pollfd fds[2]; + + /* try forever (or exit requested) until we succeed */ + while (1) { + sock = socket(AF_INET, SOCK_STREAM, 0); + DevAssert(sock != -1); + int v = 1; + r = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v)); + DevAssert(r == 0); + /* unblock socket */ + opts = fcntl(sock, F_GETFL); + DevAssert(opts >= 0); + opts |= O_NONBLOCK; + r = fcntl(sock, F_SETFL, opts); + DevAssert(r == 0); + if (connect(sock, (struct sockaddr *)&time_client->server_ip, sizeof(time_client->server_ip)) == 0) + break; + if (errno == EINPROGRESS) { + /* connection not completed yet, we need to wait */ + fds[0].fd = sock; + fds[0].events = POLLOUT; + fds[1].fd = time_client->exit_fd; + fds[1].events = POLLIN; + r = poll(fds, 2, -1); + DevAssert(r >= 0); + /* do we need to exit? */ + if (fds[1].revents) { + shutdown(sock, SHUT_RDWR); + close(sock); + sock = -1; + break; + } + /* check if connection is successful */ + if (fds[0].revents & POLLOUT) { + int so_error; + socklen_t so_error_len = sizeof(so_error); + r = getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_error, &so_error_len); + DevAssert(r == 0 && so_error_len == sizeof(so_error)); + if (so_error == 0) + /* success */ + break; + } + } + /* connection failed, try again in 1s (or stop if exit requested) */ + close(sock); + sock = -1; + char server_address[256]; + const char *ret = inet_ntop(AF_INET, &time_client->server_ip.sin_addr, server_address, sizeof(server_address)); + DevAssert(ret != NULL); + LOG_W(UTIL, "time client: connection to %s:%d failed, try again in 1s\n", server_address, time_client->server_port); + /* sleep 1s (or exit requested) */ + fds[0].fd = time_client->exit_fd; + fds[0].events = POLLIN; + r = poll(fds, 1, 1000); + DevAssert(r >= 0); + /* exit requested? */ + if (fds[0].revents) { + shutdown(sock, SHUT_RDWR); + close(sock); + sock = -1; + break; + } + } + if (sock != -1) { + /* reblock socket */ + opts &= ~O_NONBLOCK; + r = fcntl(sock, F_SETFL, opts); + DevAssert(r == 0); + } + return sock; +} + +static void *time_client_thread(void *tc) +{ + time_client_private_t *time_client = tc; + int ret; + int socket = connect_to_server(time_client); + /* if socket is -1 at this point, it means that exit was requested */ + if (socket == -1) + return NULL; + + while (1) { + struct pollfd polls[2]; + /* polls[0] is for the socket */ + polls[0].fd = socket; + polls[0].events = POLLIN; + + /* polls[1] is for exit_fd */ + polls[1].fd = time_client->exit_fd; + polls[1].events = POLLIN; + + ret = poll(polls, 2, -1); + DevAssert(ret >= 0); + + /* whatever is in revents for exit_fd means exit */ + if (polls[1].revents) + break; + + /* is socket fine? */ + if (polls[0].revents & (POLLERR | POLLHUP)) { +reconnect: + /* socket not fine, reconnect */ + shutdown(socket, SHUT_RDWR); + close(socket); + socket = connect_to_server(time_client); + /* connect only fails if exit requested, we can safely 'continue' */ + continue; + } + + /* here we only accept POLLIN */ + DevAssert(polls[0].revents == POLLIN); + + /* get the ticks from server */ + uint8_t tick_count; + ret = read(socket, &tick_count, 1); + /* any error => reconnect */ + if (ret == -1) { + LOG_E(UTIL, "time client: error %s, reconnect\n", strerror(errno)); + goto reconnect; + } + /* if 0 is returned, it means socket has been closed by other end */ + if (ret == 0) { + LOG_E(UTIL, "time client: socket closed by other end, reconnect\n"); + goto reconnect; + } + DevAssert(ret == 1); + + /* call callback once for each tick */ + for (int i = 0; i < tick_count; i++) + time_client->callback(time_client->callback_data); + } + + shutdown(socket, SHUT_RDWR); + close(socket); + + return NULL; +} + +time_client_t *new_time_client(const char *server_ip, + int server_port, + void (*callback)(void *), + void *callback_data) +{ + time_client_private_t *tc; + int ret; + + tc = calloc(1, sizeof(*tc)); + DevAssert(tc != NULL); + + tc->exit_fd = eventfd(0, 0); + DevAssert(tc->exit_fd != -1); + + ret = inet_aton(server_ip, &tc->server_ip.sin_addr); + DevAssert(ret != 0); + tc->server_ip.sin_family = AF_INET; + tc->server_ip.sin_port = htons(server_port); + tc->server_port = server_port; + tc->callback = callback; + tc->callback_data = callback_data; + + threadCreate(&tc->thread_id, time_client_thread, tc, "time client", -1, SCHED_OAI); + + return tc; +} + +void free_time_client(time_client_t *tc) +{ + time_client_private_t *time_client = tc; + + uint64_t v = 1; + int ret = write(time_client->exit_fd, &v, 8); + DevAssert(ret == 8); + + ret = pthread_join(time_client->thread_id, NULL); + DevAssert(ret == 0); + + close(time_client->exit_fd); + free(time_client); +} diff --git a/common/utils/time_manager/time_client.h b/common/utils/time_manager/time_client.h new file mode 100644 index 0000000000..02905896a4 --- /dev/null +++ b/common/utils/time_manager/time_client.h @@ -0,0 +1,36 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef COMMON_UTIL_TIME_MANAGER_TIME_CLIENT +#define COMMON_UTIL_TIME_MANAGER_TIME_CLIENT + +/* opaque data type */ +typedef void time_client_t; + +time_client_t *new_time_client(const char *server_ip, + int server_port, + void (*callback)(void *), + void *callback_data); + +void free_time_client(time_client_t *time_client); + + +#endif /* COMMON_UTIL_TIME_MANAGER_TIME_CLIENT */ diff --git a/common/utils/time_manager/time_manager.c b/common/utils/time_manager/time_manager.c new file mode 100644 index 0000000000..cf8168de0b --- /dev/null +++ b/common/utils/time_manager/time_manager.c @@ -0,0 +1,240 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#define _GNU_SOURCE +#include + +#include "time_manager.h" + +#include "time_source.h" +#include "time_server.h" +#include "time_client.h" + +#include "common/config/config_userapi.h" +#include "common/utils/assertions.h" +#include "common/utils/LOG/log.h" + +/* global variables (some may be ununsed, depends on configuration) */ +static time_source_t *time_source = NULL; +static time_server_t *time_server = NULL; +static time_client_t *time_client = NULL; + +/* available tick functions in the executable + * found at startup time of the program using dlsym() + */ +static void (*pdcp_tick)(void) = NULL; +static void (*rlc_tick)(void) = NULL; +static void (*x2ap_tick)(void) = NULL; + +/* tick callback, called by time_source every 'millisecond' (can be + * actual millisecond or simulated millisecond) + */ +static void tick(void *unused) +{ + /* call x2ap_tick, pdcp_tick, rlc_tick (if they exist) */ + if (pdcp_tick != NULL) + pdcp_tick(); + + if (rlc_tick != NULL) + rlc_tick(); + + if (x2ap_tick != NULL) + x2ap_tick(); +} + +void time_manager_start(time_manager_client_t client_type, + time_manager_mode_t running_mode) +{ + time_source_type_t time_source_type = -1; + bool has_time_server = false; + bool has_time_client = false; + char *default_server_ip = "127.0.0.1"; + int default_server_port = 7374; + char *server_ip = NULL; + int server_port = -1; + + /* retrieve, if applicable, available tick functions in the program */ + if (client_type == TIME_MANAGER_GNB_MONOLITHIC + || client_type == TIME_MANAGER_GNB_CU + || client_type == TIME_MANAGER_UE) + pdcp_tick = dlsym(RTLD_DEFAULT, "nr_pdcp_ms_tick"); + + if (client_type == TIME_MANAGER_GNB_MONOLITHIC + || client_type == TIME_MANAGER_GNB_DU + || client_type == TIME_MANAGER_UE) + rlc_tick = dlsym(RTLD_DEFAULT, "nr_rlc_ms_tick"); + + /* let's also retrieve is_x2ap_enabled() with dlsym to not force the + * application to define it (this logic may be changed if wanted) + */ + int (*is_x2ap_enabled)(void) = dlsym(RTLD_DEFAULT, "is_x2ap_enabled"); + if ((client_type == TIME_MANAGER_GNB_MONOLITHIC + || client_type == TIME_MANAGER_GNB_CU) + && is_x2ap_enabled != NULL && is_x2ap_enabled()) + x2ap_tick = dlsym(RTLD_DEFAULT, "x2ap_ms_tick"); + + /* get default configuration */ + switch (client_type) { + case TIME_MANAGER_GNB_MONOLITHIC: + case TIME_MANAGER_GNB_DU: + case TIME_MANAGER_UE: + switch (running_mode) { + case TIME_MANAGER_REALTIME: + time_source_type = TIME_SOURCE_REALTIME; + break; + case TIME_MANAGER_IQ_SAMPLES: + time_source_type = TIME_SOURCE_IQ_SAMPLES; + break; + } + break; + case TIME_MANAGER_GNB_CU: + case TIME_MANAGER_SIMULATOR: + time_source_type = TIME_SOURCE_REALTIME; + break; + } + + /* get values from configuration */ + paramdef_t config_parameters[] = { + {"time_source", "time source", 0, .strptr = NULL, .defstrval = NULL, TYPE_STRING, 0}, + {"mode", "time mode", 0, .strptr = NULL, .defstrval = NULL, TYPE_STRING, 0}, + {"server_ip", "server ip", 0, .strptr = NULL, .defstrval = NULL, TYPE_STRING, 0}, + {"server_port", "server port", 0, .iptr = NULL, .defintval = -1, TYPE_INT, 0}, + }; + int ret = config_get(config_get_if(), config_parameters, sizeofArray(config_parameters), "time_management"); + if (ret >= 0) { +#define TIME_SOURCE_IDX 0 +#define MODE_IDX 1 +#define SERVER_IP_IDX 2 +#define SERVER_PORT_IDX 3 + char *param; + /* time source */ + if (config_parameters[TIME_SOURCE_IDX].strptr != NULL) + param = *config_parameters[TIME_SOURCE_IDX].strptr; + else + param = NULL; + if (param != NULL) { + if (!strcmp(param, "realtime")) { + time_source_type = TIME_SOURCE_REALTIME; + } else if (!strcmp(param, "iq_samples")) { + time_source_type = TIME_SOURCE_IQ_SAMPLES; + } else { + AssertFatal(0, "bad parameter 'time_source' in section 'time_management', unknown value \"%s\". Valid values are \"realtime\" and \"iq_samples\"\n", param); + } + } + + /* mode */ + if (config_parameters[MODE_IDX].strptr != NULL) + param = *config_parameters[MODE_IDX].strptr; + else + param = NULL; + if (param != NULL) { + if (!strcmp(param, "standalone")) { + has_time_server = false; + has_time_client = false; + } else if (!strcmp(param, "server")) { + has_time_server = true; + has_time_client = false; + } else if (!strcmp(param, "client")) { + has_time_server = false; + has_time_client = true; + } else { + AssertFatal(0, "bad parameter 'mode' in section 'time_management', unknown value \"%s\". Valid values are \"standalone\", \"server\" and \"client\"\n", param); + } + } + + /* server ip */ + if (config_parameters[SERVER_IP_IDX].strptr != NULL) + param = *config_parameters[SERVER_IP_IDX].strptr; + else + param = NULL; + if (param != NULL) { + server_ip = param; + } + + /* server port */ + int port = *config_parameters[SERVER_PORT_IDX].iptr; + if (port != -1) { + server_port = port; + } + } + + if (server_ip == NULL) + server_ip = default_server_ip; + + if (server_port == -1) + server_port = default_server_port; + + /* create entities, according to selected configuration */ + if (!has_time_client) { + time_source = new_time_source(time_source_type); + time_source_set_callback(time_source, tick, NULL); + } + + if (has_time_server) { + time_server = new_time_server(server_ip, server_port, tick, NULL); + time_server_attach_time_source(time_server, time_source); + } + + if (has_time_client) { + time_client = new_time_client(server_ip, server_port, tick, NULL); + } + + LOG_I(UTIL, + "time manager configuration: [time source: %s] [mode: %s] [server IP: %s} [server port: %d]%s\n", + (char *[]){"reatime", "iq_samples"}[time_source_type], + has_time_server ? "server" + : has_time_client ? "client" + : "standalone", + server_ip, + server_port, + has_time_server || has_time_client ? "" : " (server IP/port not used)"); + + LOG_I(UTIL, + "time manager: pdcp tick: %s, rlc tick: %s, x2ap tick: %s\n", + pdcp_tick == NULL ? "not activated" : "activated", + rlc_tick == NULL ? "not activated" : "activated", + x2ap_tick == NULL ? "not activated" : "activated"); +} + +void time_manager_iq_samples(uint64_t iq_samples_count, + uint64_t iq_samples_per_second) +{ + time_source_iq_add(time_source, iq_samples_count, iq_samples_per_second); +} + +void time_manager_finish(void) +{ + /* time source has to be shutdown first */ + if (time_source != NULL) { + free_time_source(time_source); + time_source = NULL; + } + + if (time_server != NULL) { + free_time_server(time_server); + time_server = NULL; + } + + if (time_client != NULL) { + free_time_client(time_client); + time_client = NULL; + } +} diff --git a/common/utils/time_manager/time_manager.h b/common/utils/time_manager/time_manager.h new file mode 100644 index 0000000000..d1ed4ed0e1 --- /dev/null +++ b/common/utils/time_manager/time_manager.h @@ -0,0 +1,46 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef COMMON_UTIL_TIME_MANAGER_TIME_MANAGER +#define COMMON_UTIL_TIME_MANAGER_TIME_MANAGER + +#include + +typedef enum { + TIME_MANAGER_GNB_MONOLITHIC, + TIME_MANAGER_GNB_CU, + TIME_MANAGER_GNB_DU, + TIME_MANAGER_UE, + TIME_MANAGER_SIMULATOR, +} time_manager_client_t; + +typedef enum { + TIME_MANAGER_REALTIME, + TIME_MANAGER_IQ_SAMPLES +} time_manager_mode_t; + +void time_manager_start(time_manager_client_t client_type, + time_manager_mode_t running_mode); +void time_manager_iq_samples(uint64_t iq_samples_count, + uint64_t iq_samples_per_second); +void time_manager_finish(void); + +#endif /* COMMON_UTIL_TIME_MANAGER_TIME_MANAGER */ diff --git a/common/utils/time_manager/time_server.c b/common/utils/time_manager/time_server.c new file mode 100644 index 0000000000..83b9e30fd2 --- /dev/null +++ b/common/utils/time_manager/time_server.c @@ -0,0 +1,251 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include "time_server.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/utils/system.h" +#include "common/utils/LOG/log.h" + +typedef struct { + pthread_t thread_id; + _Atomic bool exit; + int server_socket; + int tick_fd; + void (*callback)(void *); + void *callback_data; +} time_server_private_t; + +static void *time_server_thread(void *ts) +{ + time_server_private_t *time_server = ts; + struct pollfd *polls = NULL; + int *socket_fds = NULL; + int client_count = 0; + + polls = calloc(2, sizeof(struct pollfd)); + DevAssert(polls != NULL); + + while (!time_server->exit) { + /* polls[0] is to receive ticks */ + polls[0].fd = time_server->tick_fd; + polls[0].events = POLLIN; + /* polls[1] is to accept connections from clients */ + polls[1].fd = time_server->server_socket; + polls[1].events = POLLIN; + + /* add clients */ + for (int i = 0; i < client_count; i++) { + polls[i + 2].fd = socket_fds[i]; + polls[i + 2].events = POLLIN; + } + + int ret = poll(polls, client_count + 2, -1); + DevAssert(ret >= 1 || (ret == -1 && errno == EINTR)); + + if (time_server->exit) + break; + + /* check tick */ + if (polls[0].revents) { + /* do not accept errors */ + DevAssert(polls[0].revents == POLLIN); + uint64_t ticks; + int ret = read(time_server->tick_fd, &ticks, 8); + DevAssert(ret == 8); + /* call the callback ticks time */ + for (int i = 0; i < ticks; i++) + time_server->callback(time_server->callback_data); + /* send ticks by blocks of 255 max (what fits in a uint8_t) */ + while (ticks) { + /* get min(ticks, 255) */ + uint8_t count = ticks < 255 ? ticks : 255; + /* send to all clients */ + for (int i = 0; i < client_count; i++) { + if (write(socket_fds[i], &count, 1) == 1) + continue; + + /* error writing - remove client */ + LOG_W(UTIL, "time server: client disconnects\n"); + shutdown(socket_fds[i], SHUT_RDWR); + close(socket_fds[i]); + client_count--; + i--; + if (client_count == 0) { + free(socket_fds); + socket_fds = NULL; + } else { + memmove(&socket_fds[i + 1], &socket_fds[i + 2], sizeof(int) * (client_count - i - 1)); + socket_fds = realloc(socket_fds, sizeof(int) * client_count); + } + polls = realloc(polls, sizeof(struct pollfd) * (client_count + 2)); + DevAssert(polls != NULL); + } + ticks -= count; + } + } + + /* check new client */ + if (polls[1].revents) { + /* do not accept errors */ + DevAssert(polls[1].revents == POLLIN); + + int new_socket = accept(time_server->server_socket, NULL, NULL); + DevAssert(new_socket != -1); + + /* add client */ + client_count++; + socket_fds = realloc(socket_fds, sizeof(int) * client_count); + DevAssert(socket_fds != NULL); + socket_fds[client_count - 1] = new_socket; + polls = realloc(polls, sizeof(struct pollfd) * (client_count + 2)); + DevAssert(polls != NULL); + continue; + } + + /* any event on a client socket is to be considered as an error */ + for (int i = 0; i < client_count; i++) { + if (polls[i + 2].revents == 0) + continue; + + LOG_W(UTIL, + "time server: error on client's socket, removing client (revents is %d, fd is %d i is %d)\n", + polls[i + 2].revents, + polls[i + 2].fd, + i); + shutdown(socket_fds[i], SHUT_RDWR); + close(socket_fds[i]); + client_count--; + i--; + if (client_count == 0) { + free(socket_fds); + socket_fds = NULL; + } else { + memmove(&socket_fds[i + 1], &socket_fds[i + 2], sizeof(int) * (client_count - i - 1)); + socket_fds = realloc(socket_fds, sizeof(int) * client_count); + DevAssert(socket_fds != NULL); + } + if (client_count) + memmove(&polls[i + 2 + 1], &polls[i + 2 + 2], sizeof(struct pollfd) * (client_count - i - 1)); + polls = realloc(polls, sizeof(struct pollfd) * (client_count + 2)); + DevAssert(polls != NULL); + } + } + + /* close all clients' sockets */ + for (int i = 0; i < client_count; i++) { + shutdown(socket_fds[i], SHUT_RDWR); + close(socket_fds[i]); + } + + free(polls); + free(socket_fds); + + return NULL; +} + +time_server_t *new_time_server(const char *ip, + int port, + void (*callback)(void *), + void *callback_data) +{ + time_server_private_t *ts; + int ret; + + ts = calloc(1, sizeof(*ts)); + DevAssert(ts != NULL); + + ts->server_socket = socket(AF_INET, SOCK_STREAM, 0); + DevAssert(ts->server_socket != -1); + + int v = 1; + ret = setsockopt(ts->server_socket, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)); + DevAssert(ret == 0); + v = 1; + ret = setsockopt(ts->server_socket, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v)); + DevAssert(ret == 0); + ts->tick_fd = eventfd(0, 0); + DevAssert(ts->tick_fd != -1); + + struct sockaddr_in ip_addr; + ip_addr.sin_family = AF_INET; + ip_addr.sin_port = htons(port); + ret = inet_aton(ip, &ip_addr.sin_addr); + DevAssert(ret != 0); + ret = bind(ts->server_socket, (struct sockaddr *)&ip_addr, sizeof(ip_addr)); + DevAssert(ret == 0); + + ret = listen(ts->server_socket, 10); /* 10: arbitrary value */ + DevAssert(ret == 0); + + ts->callback = callback; + ts->callback_data = callback_data; + + threadCreate(&ts->thread_id, time_server_thread, ts, "time server", -1, SCHED_OAI); + return ts; +} + +void free_time_server(time_server_t *ts) +{ + time_server_private_t *time_server = ts; + + /* flag exit */ + time_server->exit = true; + + /* send a fake tick to wake up the server thread */ + uint64_t v = 1; + int ret = write(time_server->tick_fd, &v, 8); + DevAssert(ret == 8); + + /* wait for thread termination */ + void *retval; + ret = pthread_join(time_server->thread_id, &retval); + + /* free memory */ + shutdown(time_server->server_socket, SHUT_RDWR); + close(time_server->server_socket); + close(time_server->tick_fd); + free(time_server); +} + +static void time_server_tick_callback(void *ts) +{ + time_server_private_t *time_server = ts; + uint64_t v = 1; + int ret = write(time_server->tick_fd, &v, 8); + DevAssert(ret == 8); +} + +void time_server_attach_time_source(time_server_t *time_server, + time_source_t *time_source) +{ + time_source_set_callback(time_source, time_server_tick_callback, time_server); +} diff --git a/common/utils/time_manager/time_server.h b/common/utils/time_manager/time_server.h new file mode 100644 index 0000000000..b3f41b1941 --- /dev/null +++ b/common/utils/time_manager/time_server.h @@ -0,0 +1,40 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef COMMON_UTIL_TIME_MANAGER_TIME_SERVER +#define COMMON_UTIL_TIME_MANAGER_TIME_SERVER + +#include "time_source.h" + +/* opaque data type */ +typedef void time_server_t; + +time_server_t *new_time_server(const char *ip, + int port, + void (*callback)(void *), + void *callback_data); +void free_time_server(time_server_t *time_server); + +void time_server_attach_time_source(time_server_t *time_server, + time_source_t *time_source); + +#endif /* COMMON_UTIL_TIME_MANAGER_TIME_SERVER */ + diff --git a/common/utils/time_manager/time_source.c b/common/utils/time_manager/time_source.c new file mode 100644 index 0000000000..76a76bc655 --- /dev/null +++ b/common/utils/time_manager/time_source.c @@ -0,0 +1,264 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include "time_source.h" + +#include +#include +#include +#include + +#include "common/utils/assertions.h" +#include "common/utils/system.h" + +typedef void (*callback_function_t)(void *callback_data); +typedef void * callback_data_t; + +typedef struct { + time_source_type_t type; + volatile callback_function_t callback; + volatile callback_data_t callback_data; + _Atomic bool exit; + void (*terminate)(void *this); + pthread_t thread_id; + pthread_mutex_t mutex; + pthread_cond_t cond; +} time_source_common_t; + +typedef struct { + time_source_common_t common; +} time_source_realtime_t; + +typedef struct { + time_source_common_t common; + volatile uint64_t iq_samples_count; + volatile uint64_t iq_samples_per_second; +} time_source_iq_t; + +static void init_mutex(time_source_common_t *t) +{ + int ret = pthread_mutex_init(&t->mutex, NULL); + DevAssert(ret == 0); +} + +static void init_cond(time_source_common_t *t) +{ + int ret = pthread_cond_init(&t->cond, NULL); + DevAssert(ret == 0); +} + +static void lock(time_source_common_t *t) +{ + int ret = pthread_mutex_lock(&t->mutex); + DevAssert(ret == 0); +} + +static void unlock(time_source_common_t *t) +{ + int ret = pthread_mutex_unlock(&t->mutex); + DevAssert(ret == 0); +} + +static void condwait(time_source_common_t *t) +{ + int ret = pthread_cond_wait(&t->cond, &t->mutex); + DevAssert(ret == 0); +} + +static void condsignal(time_source_common_t *t) +{ + int ret = pthread_cond_signal(&t->cond); + DevAssert(ret == 0); +} + +static void wait_thread_termination(time_source_common_t *t) +{ + void *retval; + int ret = pthread_join(t->thread_id, &retval); + DevAssert(ret == 0); +} + +static struct timespec next_ms(struct timespec t) +{ + /* go to next millisecond */ + t.tv_nsec += 1000000; + if (t.tv_nsec > 999999999) { + t.tv_nsec -= 1000000000; + t.tv_sec++; + } + + return t; +} + +static void *time_source_realtime_thread(void *ts) +{ + time_source_realtime_t *time_source = ts; + + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) + AssertFatal(0, "clock_gettime failed\n"); + + /* go to next millisecond */ + now = next_ms(now); + + while (!time_source->common.exit) { + /* sleep */ + if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &now, NULL) == -1) { + /* accept interruption by signals and then simply restart the sleep */ + if (errno == EINTR) + continue; + AssertFatal(0, "clock_nanosleep failed\n"); + } + + if (time_source->common.exit) + break; + + /* tick */ + lock(&time_source->common); + void (*callback)(void *callback_data) = time_source->common.callback; + void *callback_data = time_source->common.callback_data; + unlock(&time_source->common); + if (callback != NULL) + callback(callback_data); + + /* go to next millisecond */ + now = next_ms(now); + } + + return (void *)0; +} + +static void *time_source_iq_samples_thread(void *ts) +{ + time_source_iq_t *time_source = ts; + + while (!time_source->common.exit) { + /* wait until we have at least 1ms of IQ samples */ + lock(&time_source->common); + while (!time_source->common.exit + && (time_source->iq_samples_per_second == 0 + || time_source->iq_samples_count < time_source->iq_samples_per_second / 1000)) + condwait(&time_source->common); + time_source->iq_samples_count -= time_source->iq_samples_per_second / 1000; + unlock(&time_source->common); + + if (time_source->common.exit) + break; + + /* tick */ + lock(&time_source->common); + void (*callback)(void *callback_data) = time_source->common.callback; + void *callback_data = time_source->common.callback_data; + unlock(&time_source->common); + if (callback != NULL) + callback(callback_data); + } + + return (void *)0; +} + +static void time_source_iq_samples_terminate(void *ts) +{ + time_source_iq_t *time_source = ts; + condsignal(&time_source->common); +} + +time_source_t *new_time_source(time_source_type_t type) +{ + time_source_t *ret = NULL; + void *(*thread_function)(void *) = NULL; + void (*terminate_function)(void *) = NULL; + char *thread_function_name = NULL; + + switch (type) { + case TIME_SOURCE_REALTIME: { + time_source_realtime_t *ts = calloc(1, sizeof(*ts)); + DevAssert(ts != NULL); + thread_function = time_source_realtime_thread; + thread_function_name = "time source realtime"; + ret = ts; + break; + } + case TIME_SOURCE_IQ_SAMPLES: { + time_source_iq_t *ts = calloc(1, sizeof(*ts)); + DevAssert(ts != NULL); + thread_function = time_source_iq_samples_thread; + terminate_function = time_source_iq_samples_terminate; + thread_function_name = "time source iq samples"; + ret = ts; + break; + } + } + + time_source_common_t *c = ret; + c->type = type; + init_mutex(c); + init_cond(c); + c->terminate = terminate_function; + threadCreate(&c->thread_id, thread_function, c, thread_function_name, -1, SCHED_OAI); + + return ret; +} + +void free_time_source(time_source_t *ts) +{ + time_source_common_t *time_source = ts; + time_source->exit = true; + if (time_source->terminate != NULL) + time_source->terminate(time_source); + wait_thread_termination(time_source); + free(time_source); +} + +void time_source_set_callback(time_source_t *ts, + void (*callback)(void *), + void *callback_data) +{ + time_source_common_t *time_source = ts; + lock(time_source); + time_source->callback = callback; + time_source->callback_data = callback_data; + unlock(time_source); +} + +void time_source_iq_add(time_source_t *ts, + uint64_t iq_samples_count, + uint64_t iq_samples_per_second) +{ + if (ts == NULL) + return; + + time_source_iq_t *time_source = ts; + + if (time_source->common.type != TIME_SOURCE_IQ_SAMPLES) + return; + + lock(&time_source->common); + + time_source->iq_samples_count += iq_samples_count; + + if (time_source->iq_samples_per_second == 0) + time_source->iq_samples_per_second = iq_samples_per_second; + AssertFatal(time_source->iq_samples_per_second == iq_samples_per_second, "unsupported change of value 'IQ samples per second'\n"); + + condsignal(&time_source->common); + unlock(&time_source->common); +} diff --git a/common/utils/time_manager/time_source.h b/common/utils/time_manager/time_source.h new file mode 100644 index 0000000000..45b39d610f --- /dev/null +++ b/common/utils/time_manager/time_source.h @@ -0,0 +1,47 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef COMMON_UTIL_TIME_MANAGER_TIME_SOURCE +#define COMMON_UTIL_TIME_MANAGER_TIME_SOURCE + +#include + +/* opaque data type */ +typedef void time_source_t; + +typedef enum { + TIME_SOURCE_REALTIME, + TIME_SOURCE_IQ_SAMPLES +} time_source_type_t; + +time_source_t *new_time_source(time_source_type_t type); + +void free_time_source(time_source_t *time_source); + +void time_source_set_callback(time_source_t *time_source, + void (*callback)(void *), + void *callback_data); + +void time_source_iq_add(time_source_t *time_source, + uint64_t iq_samples_count, + uint64_t iq_samples_per_second); + +#endif /* COMMON_UTIL_TIME_MANAGER_TIME_SOURCE */ diff --git a/doc/SW_archi.md b/doc/SW_archi.md index da7f014fb7..433a1ac3d1 100644 --- a/doc/SW_archi.md +++ b/doc/SW_archi.md @@ -298,7 +298,7 @@ Even, a future evolution could remove this global rlc layer: rlc can be only a l When adding a UE, external code have to call `add_rlc_srb()` and/or `add_rlc_drb()`, to remove it: `rrc_rlc_remove_ue()` Inside UE, channels called drd or srb can be created: ??? and deleted: rrc_rlc_config_req() -nr_rlc_tick() must be called periodically to manage the internal timers +nr_rlc_ms_tick() must be called periodically to manage the internal timers successful_delivery() and max_retx_reached(): in ??? trigger, the RLC sends a itti message to RRC: RLC_SDU_INDICATION (neutralized by #if 0 right now) diff --git a/doc/time_management.md b/doc/time_management.md new file mode 100644 index 0000000000..a3ea4d580c --- /dev/null +++ b/doc/time_management.md @@ -0,0 +1,284 @@ +# Time management module + +[[_TOC_]] + +## Introduction + +Several parts of the 4G/5G protocol stack keep track of time, for example +for timeout events (in RRC, PDCP, or RLC for example). + +The obvious simple way to deal with time is to use the computer's realtime +clock. This works well when we run the stack in realtime, with real +radio equipment. But in simulation the system may run faster or slower +than realtime, so we need another way to deal with time. A good solution +is then to base time on the current IQ data processed by the system. So time +becomes "IQ samples" based, not realtime. + +The system may be decomposed into various components (CU, DU, RU, various +UEs) running either in realtime or simulation. Time may be distributed +to all these components from only one time source for accurate execution +of the scenario, or each component may have its own time source, either +based on realtime or IQ samples. + +## Overall structure + +The time management module is made of three main parts. The first one +is the time source. It can either be realtime or "IQ samples" based. +In one running scenario of the system, there can be one or more time +sources (imagine one for the gNB, and one per each UE). There can also +be only one time source, shared between various users of the time +manager. + +The second part is the server. One server is attached to one time source +and receives a tick every millisecond (real or simulated) from the time +source. It distributes the tick to the various connected clients. + +And finally the last part is the client, which connects to a single server +and receives ticks from it. The client does not have a time source attached +to it. It gets the time from the server. + +It is possible for a program (monolithic gNB for example) to have only +a time source, without server or client. + +## Configuration + +To configure the time management module, add the following section in +the configuration file. + +``` +time_management = { + #valid time sources: realtime, iq_samples + time_source = realtime + + #valid modes: standalone, server, client + mode = standalone + + #set ip/port of server (for server mode, this is the address/port to bind to; + #for client mode, this is the address/port to connect to) + server_ip = "127.0.0.1" + server_port = 7374 +} +``` + +If not set in the configuration file, some default is applied, depending +on the program run. (See the function `time_manager_start()` for details.) + +It is also possible to pass configuration parameters on the command line. +For example add `--time_manager.time_source iq_samples` to the command line +to set the time source to `iq_samples`. The same syntax is applicable to the +other parameters. + +## Examples + +Here come some examples of configuration for typical use cases of OAI. + +### Monolithic gNB realtime + +This is a very simple example. The gNB runs in `standalone` mode with +a `realtime` time source. + +``` +time_management = { + time_source = realtime + mode = standalone +} +``` +### Monolithic gNB "IQ samples time" + +For this configuration, it's the same as the previous example, but the +time source is now `iq_samples`. + +``` +time_management = { + time_source = iq_samples + mode = standalone +} +``` +### RF simulator CU/DU "IQ samples time" + +Let's suppose the CU runs on a machine with IP address 1.2.3.4 and the DU +runs on a machine with IP address 10.11.12.13. + +The DU has the time source and so is the server. The CU acts as a client. + +Here comes the configuration for the CU. + +``` +time_management = { + mode = client + server_ip = "10.11.12.13" + server_port = 7374 +} +``` +And here is the configuration for the DU. + +``` +time_management = { + time_source = iq_samples + mode = server + server_ip = "10.11.12.13" + server_port = 7374 +} +``` + +### RF simulator CU/DU "realtime" + +Let's suppose the CU runs on a machine with IP address 1.2.3.4 and the DU +runs on a machine with IP address 10.11.12.13. + +CU and DU can both run in standalone mode, with no exchange between them. +Any one of them can also acts as a server and the other one as a client. + +Let's suppose for this example that the CU and the DU run in client/server +mode. Let's set the CU as the time source and so as the server. The DU acts +as a client. + +Here comes the configuration for the CU. + +``` +time_management = { + time_source = realtime + mode = server + server_ip = "1.2.3.4" + server_port = 7374 +} +``` +And here is the configuration for the DU. + +``` +time_management = { + mode = client + server_ip = "1.2.3.4" + server_port = 7374 +} +``` + +Note that the time management module is flexible. It's possible to +configure more complex setups. For example the time source can be a gNB +and several UEs can be connected as client. + +One could also write a simple program acting as a time source and distributing +time to everyone (one or several gNB, several UEs). + +## Programming API + +Here comes the internal API of the time manager module. The (simpler) OAI +API is described afterwards. + +### Time source + +- opaque type: `time_source_t` +- create a time source: + ``` + time_source_t *new_time_source(time_source_type_t type) + ``` + where `type` is one of: + * `TIME_SOURCE_REALTIME` + * `TIME_SOURCE_IQ_SAMPLES` +- delete a time source: + ``` + void free_time_source(time_source_t *time_source) + ``` +- set millisecond callback: + ``` + void time_source_set_callback(time_source_t *time_source, + void (*callback)(void *), + void *callback_data) + ``` + where `callback` is a function that will be called for each millisecond using + the provided `callback_data` +- for IQ samples time management, the user of the time source must call + this function when it processes IQ samples: + ``` + void time_source_iq_add(time_source_t *time_source, + uint64_t iq_samples_count, + uint64_t iq_samples_per_second) + ``` + then the time source will generate a tick for each block for IQ samples + lasting one millisecond. (If you pass `iq_samples_count` lasting several + milliseconds then the time source will generate several ticks in a row, + one for each millisecond.) + +### Server + +- opaque tyoe: `time_server_t` +- create a server: + ``` + time_server_t *new_time_server(const char *ip, + int port, + void (*callback)(void *), + void *callback_data) + ``` + where ip/port is the IP adress and port the server will listen to + and `callback` is a function that will be called for each millisecond using + the provided `callback_data` +- delete a server: + ``` + void free_time_server(time_server_t *time_server) + ``` +- attach a time source: + ``` + void time_server_attach_time_source(time_server_t *time_server, + time_source_t *time_source) + ``` + +### Client + +- opaque type: `time_client_t` +- create a client: + ``` + time_client_t *new_time_client(const char *server_ip, + int server_port, + void (*callback)(void *), + void *callback_data) + ``` + the client will connect on given server ip/port + and use the `callback` with `callback_data` for each tick received +- delete a client: + ``` + void free_time_client(time_client_t *time_client) + ``` + +### Threading + +Each component (time source, server, client) runs its own thread which will +be the one calling the configured callback. Be careful of using proper +synchronization techniques in your callback to have correct behavior with +the other threads of the program. + +## OAI API + +OAI uses the time manager through a simplified API. All the code is contained +in `time_manager.c`, together with some global variables (so that there is no +need to pass objects around, to limit risks of misuse). + +Each program (for example: gnb, ue, cu, du) calls the `time_manager_start()` +function, passing the kind of program it is. Based on this type and the +configuration, `time_manager_start()` initializes what is needed. + +The function `time_manager_iq_samples()` is to be called by programs that +read IQ samples. It is called unconditionally. It may do nothing if the +configuration is to use realtime ticks and not IQ samples ticks. + +When the program exits, it calls `timer_manager_finish()` which in turns stops +the various threads created by `time_manager_start()` and releases all the +allocated data. + +Here comes the API. + +- init the time manager: + ``` + void time_manager_start(time_manager_client_t client_type, + time_manager_mode_t running_mode) + ``` + (for valid values of `client_type` and `running_mode` see in + `time_manager.h`) +- update IQ samples' based time manager: + ``` + void time_manager_iq_samples(uint64_t iq_samples_count, + uint64_t iq_samples_per_second) + ``` +- terminate the time manager: + ``` + void time_manager_finish(void) + ``` diff --git a/executables/nr-cuup.c b/executables/nr-cuup.c index 238ee77275..4f5520d250 100644 --- a/executables/nr-cuup.c +++ b/executables/nr-cuup.c @@ -31,6 +31,7 @@ #include "openair2/F1AP/f1ap_ids.h" #include "openair2/GNB_APP/gnb_config.h" #include "nr_pdcp/nr_pdcp_oai_api.h" +#include "common/utils/time_manager/time_manager.h" RAN_CONTEXT_t RC; THREAD_STRUCT thread_struct; @@ -139,6 +140,11 @@ int main(int argc, char **argv) exit_fun("[SOFTMODEM] Error, configuration module init failed\n"); } logInit(); + + // start time manager with some reasonable default for the running mode + // (may be overwritten in configuration file or command line) + time_manager_start(TIME_MANAGER_GNB_CU, TIME_MANAGER_REALTIME); + // strdup to put the sring in the core file for post mortem identification LOG_I(HW, "Version: %s\n", strdup(OAI_PACKAGE_VERSION)); set_softmodem_sighandler(); @@ -177,6 +183,8 @@ int main(int argc, char **argv) printf("TYPE TO TERMINATE\n"); itti_wait_tasks_end(NULL); + time_manager_finish(); + logClean(); printf("Bye.\n"); return 0; diff --git a/executables/nr-ru.c b/executables/nr-ru.c index e665fd70bb..6187c9bc2e 100644 --- a/executables/nr-ru.c +++ b/executables/nr-ru.c @@ -47,6 +47,7 @@ #include "SCHED_NR/sched_nr.h" #include "common/utils/LOG/log.h" +#include "common/utils/time_manager/time_manager.h" #include "common/utils/LOG/vcd_signal_dumper.h" #include @@ -1249,6 +1250,10 @@ void *ru_thread(void *param) slot++; } + // pretend we have 1 iq sample per slot + // and so slots_per_frame * 100 iq samples per second (1 frame being 10ms) + time_manager_iq_samples(1, fp->slots_per_frame * 100); + // synchronization on input FH interface, acquire signals/data and block LOG_D(PHY,"[RU_thread] read data: frame_rx = %d, tti_rx = %d\n", frame, slot); diff --git a/executables/nr-softmodem.c b/executables/nr-softmodem.c index 98f89c881b..f0068a8d4b 100644 --- a/executables/nr-softmodem.c +++ b/executables/nr-softmodem.c @@ -28,6 +28,7 @@ #include "PHY/TOOLS/smbv.h" unsigned short config_frames[4] = {2,9,11,13}; #endif +#include "common/utils/time_manager/time_manager.h" #ifdef ENABLE_AERIAL #include "nfapi/oai_integration/aerial/fapi_nvIPC.h" #endif @@ -628,6 +629,18 @@ int main( int argc, char **argv ) { pthread_mutex_init(&sync_mutex, NULL); } + // start time manager with some reasonable default for the running mode + // (may be overwritten in configuration file or command line) + time_manager_start(NODE_IS_MONOLITHIC(node_type) ? TIME_MANAGER_GNB_MONOLITHIC + : NODE_IS_CU(node_type) ? TIME_MANAGER_GNB_CU + : TIME_MANAGER_GNB_DU, + // iq_samples time source for monolithic/du with rfsim, + // realtime time source for other cases + IS_SOFTMODEM_RFSIM + && (NODE_IS_MONOLITHIC(node_type) || NODE_IS_DU(node_type)) + ? TIME_MANAGER_IQ_SAMPLES + : TIME_MANAGER_REALTIME); + // start the main threads number_of_cards = 1; @@ -730,6 +743,8 @@ int main( int argc, char **argv ) { pthread_cond_destroy(&nfapi_sync_cond); pthread_mutex_destroy(&nfapi_sync_mutex); + time_manager_finish(); + free(pckg); logClean(); printf("Bye.\n"); diff --git a/executables/nr-ue.c b/executables/nr-ue.c index f2716c099e..2f531592b9 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -42,6 +42,7 @@ #include "common/utils/threadPool/notified_fifo.h" #include "position_interface.h" #include "nr_phy_common.h" +#include "common/utils/time_manager/time_manager.h" /* * NR SLOT PROCESSING SEQUENCE @@ -706,16 +707,6 @@ static int UE_dl_preprocessing(PHY_VARS_NR_UE *UE, process_msg_rcc_to_mac(msg); } while (msg); - if (IS_SOFTMODEM_NOS1 || IS_SA_MODE(get_softmodem_params())) { - /* send tick to RLC and PDCP every ms */ - if (proc->nr_slot_rx % fp->slots_per_subframe == 0) { - void nr_rlc_tick(int frame, int subframe); - void nr_pdcp_tick(int frame, int subframe); - nr_rlc_tick(proc->frame_rx, proc->nr_slot_rx / fp->slots_per_subframe); - nr_pdcp_tick(proc->frame_rx, proc->nr_slot_rx / fp->slots_per_subframe); - } - } - if (UE->if_inst) UE->if_inst->slot_indication(UE->Mod_id, false); @@ -1095,6 +1086,10 @@ void *UE_thread(void *arg) absolute_slot++; TracyCFrameMark; + // pretend we have 1 iq sample per slot + // and so nb_slot_frame * 100 iq samples per second (1 frame being 10ms) + time_manager_iq_samples(1, nb_slot_frame * 100); + int slot_nr = absolute_slot % nb_slot_frame; nr_rxtx_thread_data_t curMsg = {0}; curMsg.UE=UE; diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index 6d3efc28cd..fa5e5ca259 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -60,6 +60,7 @@ unsigned short config_frames[4] = {2,9,11,13}; #endif #include "common/utils/LOG/log.h" +#include "common/utils/time_manager/time_manager.h" #include "common/utils/LOG/vcd_signal_dumper.h" #include "UTIL/OPT/opt.h" @@ -461,6 +462,14 @@ int main(int argc, char **argv) if (IS_SOFTMODEM_RFSIM && !get_softmodem_params()->phy_test) sleep(3); + // start time manager with some reasonable default for the running mode + // (may be overwritten in configuration file or command line) + time_manager_start(TIME_MANAGER_UE, + // iq_samples time source for rfsim, + // realtime time source if not + IS_SOFTMODEM_RFSIM ? TIME_MANAGER_IQ_SAMPLES + : TIME_MANAGER_REALTIME); + if (!get_softmodem_params()->nsa && get_softmodem_params()->emulate_l1) start_oai_nrue_threads(); @@ -586,6 +595,9 @@ int main(int argc, char **argv) } free_nrLDPC_coding_interface(&nrLDPC_coding_interface); + + time_manager_finish(); + free(pckg); return 0; } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c index e14004edc3..ab34ce551c 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler.c @@ -177,16 +177,6 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, slot_t slo start_meas(&gNB->eNB_scheduler); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_gNB_DLSCH_ULSCH_SCHEDULER,VCD_FUNCTION_IN); - /* send tick to RLC, PDCP, and X2AP every ms */ - if ((slot & ((1 << *scc->ssbSubcarrierSpacing) - 1)) == 0) { - void nr_rlc_tick(int frame, int subframe); - void nr_pdcp_tick(int frame, int subframe); - nr_rlc_tick(frame, slot >> *scc->ssbSubcarrierSpacing); - nr_pdcp_tick(frame, slot >> *scc->ssbSubcarrierSpacing); - if (is_x2ap_enabled()) - x2ap_trigger(); - } - for (int CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) { int num_beams = 1; if(gNB->beam_info.beam_allocation) diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index 94df409113..c206d2d30f 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -74,11 +74,6 @@ static nr_pdcp_ue_manager_t *nr_pdcp_ue_manager; -/* TODO: handle time a bit more properly */ -static uint64_t nr_pdcp_current_time; -static int nr_pdcp_current_time_last_frame; -static int nr_pdcp_current_time_last_subframe; - /* necessary globals for OAI, not used internally */ hash_table_t *pdcp_coll_p; static uint64_t pdcp_optmask; @@ -422,9 +417,6 @@ void nr_pdcp_layer_init(void) nr_pdcp_e1_if_init(node_type == ngran_gNB_CUUP || node_type == ngran_gNB_CUCP); init_nr_pdcp_data_ind_queue(); nr_pdcp_init_timer_thread(nr_pdcp_ue_manager); - if (NODE_IS_CU(node_type)) { - nr_pdcp_init_tick_thread(); - } } #include "nfapi/oai_integration/vendor_ext.h" @@ -1052,17 +1044,6 @@ bool cu_f1u_data_req(protocol_ctxt_t *ctxt_pP, return ret; } -void nr_pdcp_tick(int frame, int subframe) -{ - if (frame != nr_pdcp_current_time_last_frame || - subframe != nr_pdcp_current_time_last_subframe) { - nr_pdcp_current_time_last_frame = frame; - nr_pdcp_current_time_last_subframe = subframe; - nr_pdcp_current_time++; - nr_pdcp_wakeup_timer_thread(nr_pdcp_current_time); - } -} - /* * For the SDAP API */ diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h index 286d072019..a77cc895e6 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h @@ -127,8 +127,6 @@ bool nr_pdcp_data_req_drb(protocol_ctxt_t *ctxt_pP, const uint32_t *const sourceL2Id, const uint32_t *const destinationL2Id); -void nr_pdcp_tick(int frame, int subframe); - nr_pdcp_ue_manager_t *nr_pdcp_sdap_get_ue_manager(); int nr_pdcp_get_num_ues(ue_id_t *ue_list, int len); diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_timer_thread.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_timer_thread.c index 1c5e926fea..e63875a344 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_timer_thread.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_timer_thread.c @@ -70,26 +70,6 @@ static void *nr_pdcp_timer_thread(void *_nr_pdcp_ue_manager) return NULL; } -static void *nr_pdcp_tick_thread() { - pthread_setname_np(pthread_self(),"nr_pdcp_tick_thread"); - uint64_t curr_time = 0; - while (1) { - usleep(1000); - curr_time++; - nr_pdcp_wakeup_timer_thread(curr_time); - } - return NULL; -} - -void nr_pdcp_init_tick_thread() -{ - pthread_t t; - if (pthread_create(&t, NULL, nr_pdcp_tick_thread, NULL) != 0) { - LOG_E(PDCP, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__); - exit(1); - } -} - void nr_pdcp_init_timer_thread(nr_pdcp_ue_manager_t *nr_pdcp_ue_manager) { pthread_t t; @@ -100,10 +80,10 @@ void nr_pdcp_init_timer_thread(nr_pdcp_ue_manager_t *nr_pdcp_ue_manager) } } -void nr_pdcp_wakeup_timer_thread(uint64_t time) +void nr_pdcp_ms_tick(void) { if (pthread_mutex_lock(&timer_thread_mutex)) abort(); - timer_thread_curtime = time; + timer_thread_curtime++; if (pthread_cond_broadcast(&timer_thread_cond)) abort(); if (pthread_mutex_unlock(&timer_thread_mutex)) abort(); } diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_timer_thread.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_timer_thread.h index 552d5da279..90ff184d3d 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_timer_thread.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_timer_thread.h @@ -26,8 +26,7 @@ #include -void nr_pdcp_init_tick_thread(); void nr_pdcp_init_timer_thread(nr_pdcp_ue_manager_t *nr_pdcp_ue_manager); -void nr_pdcp_wakeup_timer_thread(uint64_t time); +void nr_pdcp_ms_tick(void); #endif /* _NR_PDCP_TIMER_THREAD_H_ */ diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c b/openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c index 4b7202f6fd..b48b03847f 100644 --- a/openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c +++ b/openair2/LAYER2/nr_rlc/nr_rlc_oai_api.c @@ -50,8 +50,6 @@ static nr_rlc_ue_manager_t *nr_rlc_ue_manager; /* TODO: handle time a bit more properly */ static pthread_mutex_t nr_rlc_current_time_mutex = PTHREAD_MUTEX_INITIALIZER; static uint64_t nr_rlc_current_time; -static int nr_rlc_current_time_last_frame; -static int nr_rlc_current_time_last_subframe; void lock_nr_rlc_current_time(void) { @@ -956,15 +954,10 @@ void nr_rlc_test_trigger_reestablishment(int ue_id) nr_rlc_manager_unlock(nr_rlc_ue_manager); } -void nr_rlc_tick(int frame, int subframe) +void nr_rlc_ms_tick(void) { lock_nr_rlc_current_time(); - if (frame != nr_rlc_current_time_last_frame || - subframe != nr_rlc_current_time_last_subframe) { - nr_rlc_current_time_last_frame = frame; - nr_rlc_current_time_last_subframe = subframe; - nr_rlc_current_time++; - } + nr_rlc_current_time++; unlock_nr_rlc_current_time(); } diff --git a/openair2/X2AP/x2ap_eNB.c b/openair2/X2AP/x2ap_eNB.c index d0d2b03304..6bc37771e0 100644 --- a/openair2/X2AP/x2ap_eNB.c +++ b/openair2/X2AP/x2ap_eNB.c @@ -756,7 +756,7 @@ mutex_error: exit(1); } -void x2ap_trigger(void) +void x2ap_ms_tick(void) { MessageDef *msg = itti_alloc_new_message(TASK_X2AP, 0, X2AP_SUBFRAME_PROCESS); itti_send_msg_to_task(TASK_X2AP, 0, msg); diff --git a/openair2/X2AP/x2ap_eNB.h b/openair2/X2AP/x2ap_eNB.h index 0f83ca64af..afa6f11b8e 100644 --- a/openair2/X2AP/x2ap_eNB.h +++ b/openair2/X2AP/x2ap_eNB.h @@ -47,7 +47,6 @@ int x2ap_eNB_init_sctp (x2ap_eNB_instance_t *instance_p, void *x2ap_task(void *arg); int is_x2ap_enabled(void); -void x2ap_trigger(void); #endif /* X2AP_H_ */ diff --git a/openair2/X2AP/x2ap_timers.c b/openair2/X2AP/x2ap_timers.c index ac619d5b0f..2e72aea408 100644 --- a/openair2/X2AP/x2ap_timers.c +++ b/openair2/X2AP/x2ap_timers.c @@ -52,7 +52,8 @@ void x2ap_check_timers(instance_t instance) int x2_ongoing; instance_p = x2ap_eNB_get_instance(instance); - DevAssert(instance_p != NULL); + if (instance_p == NULL) + return; t = &instance_p->timers; m = &instance_p->id_manager;