Merge remote-tracking branch 'origin/feature-time-manager' into integration_2025_w16 (!3071)
time management module introduction of a time management module
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
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_tick_function_t tick_functions[] = {
|
||||
nr_pdcp_ms_tick,
|
||||
nr_rlc_ms_tick,
|
||||
x2ap_ms_tick
|
||||
};
|
||||
int tick_functions_count = sizeofArray(tick_functions);
|
||||
time_manager_start(tick_functions, tick_functions_count, TIME_SOURCE_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_tick_function_t tick_functions[] = {
|
||||
nr_pdcp_ms_tick,
|
||||
nr_rlc_ms_tick,
|
||||
x2ap_ms_tick
|
||||
};
|
||||
int tick_functions_count = sizeofArray(tick_functions);
|
||||
time_manager_start(tick_functions, tick_functions_count, TIME_SOURCE_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)
|
||||
{
|
||||
time_manager_tick_function_t tick_functions[] = {
|
||||
nr_pdcp_ms_tick,
|
||||
nr_rlc_ms_tick,
|
||||
x2ap_ms_tick
|
||||
};
|
||||
int tick_functions_count = sizeofArray(tick_functions);
|
||||
/* last argument doesn't matter, overwritten by args of execl in client_server() */
|
||||
time_manager_start(tick_functions, tick_functions_count, TIME_SOURCE_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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <signal.h>
|
||||
|
||||
#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 <ip address (default 127.0.0.1)>\n");
|
||||
printf(" use this ip address for server\n");
|
||||
printf(" -port <port (default 7473)\n");
|
||||
printf(" use this port for server\n");
|
||||
printf(" -realtime\n");
|
||||
printf(" run with realtime clock (default is simulated iq samples)\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void server_callback(void *callback_data)
|
||||
{
|
||||
printf("server_callback called (callback_data %p)\n", callback_data);
|
||||
DevAssert(callback_data == (void *)1);
|
||||
}
|
||||
|
||||
static void client_callback(void *callback_data)
|
||||
{
|
||||
printf("client_callback called (callback_data %p)\n", callback_data);
|
||||
DevAssert(callback_data == (void *)2);
|
||||
}
|
||||
|
||||
/* dummy sig handler to quit immediately when pressing enter
|
||||
* (we send a signal to iq_generate_thread to interrupt sleep())
|
||||
*/
|
||||
static void sig_handle(int n)
|
||||
{
|
||||
/* nothing */
|
||||
}
|
||||
|
||||
int main(int n, char **v)
|
||||
{
|
||||
time_source_t *ts = NULL;
|
||||
time_server_t *server = NULL;
|
||||
time_client_t *client = NULL;
|
||||
int mode = STANDALONE;
|
||||
char *server_ip = "127.0.0.1";
|
||||
int server_port = 7473;
|
||||
time_source_type_t time_source_type = TIME_SOURCE_IQ_SAMPLES;
|
||||
pthread_t iq_thread;
|
||||
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (!strcmp(v[i], "-client")) { mode = CLIENT; continue; }
|
||||
if (!strcmp(v[i], "-server")) { mode = SERVER; continue; }
|
||||
if (!strcmp(v[i], "-ip")) { if (i>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;
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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 <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <poll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
AssertFatal(sock != -1, "socket() failed: %s\n", strerror(errno));
|
||||
int v = 1;
|
||||
r = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v));
|
||||
AssertFatal(r == 0, "setsockopt() failed: %s\n", strerror(errno));
|
||||
/* unblock socket */
|
||||
opts = fcntl(sock, F_GETFL);
|
||||
AssertFatal(opts >= 0, "fcntl() failed: %s\n", strerror(errno));
|
||||
opts |= O_NONBLOCK;
|
||||
r = fcntl(sock, F_SETFL, opts);
|
||||
AssertFatal(r == 0, "fctnl() failed: %s\n", strerror(errno));
|
||||
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);
|
||||
AssertFatal(r >= 0, "poll() failed: %s\n", strerror(errno));
|
||||
/* 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);
|
||||
AssertFatal(r == 0 && so_error_len == sizeof(so_error),
|
||||
"getsockopt() failed (r = %d) or so_error_len (= %d) is not %ld\n",
|
||||
r, 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));
|
||||
AssertFatal(ret != NULL, "inet_ntop() failed: %s\n", strerror(errno));
|
||||
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);
|
||||
AssertFatal(r >= 0, "poll() failed: %s\n", strerror(errno));
|
||||
/* 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);
|
||||
AssertFatal(r == 0, "fcntl() failed: %s\n", strerror(errno));
|
||||
}
|
||||
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);
|
||||
AssertFatal(ret >= 0, "poll() failed: %s\n", strerror(errno));
|
||||
|
||||
/* 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 */
|
||||
AssertFatal(polls[0].revents == POLLIN, "poll() did not return expected POLLIN\n");
|
||||
|
||||
/* 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_or_fail(1, sizeof(*tc));
|
||||
|
||||
tc->exit_fd = eventfd(0, 0);
|
||||
AssertFatal(tc->exit_fd != -1, "eventfd() failed: %s\n", strerror(errno));
|
||||
|
||||
ret = inet_aton(server_ip, &tc->server_ip.sin_addr);
|
||||
AssertFatal(ret != 0, "inet_aton() failed: %s\n", strerror(errno));
|
||||
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);
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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_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;
|
||||
|
||||
/* tick functions registered at initialization of the time manager */
|
||||
static time_manager_tick_function_t *tick_functions;
|
||||
static int tick_functions_count;
|
||||
|
||||
/* tick callback, called by time_source every 'millisecond' (can be
|
||||
* actual millisecond or simulated millisecond)
|
||||
*/
|
||||
static void tick(void *unused)
|
||||
{
|
||||
for (int i = 0; i < tick_functions_count; i++)
|
||||
tick_functions[i]();
|
||||
}
|
||||
|
||||
#define TIME_SOURCE "time_source"
|
||||
#define MODE "mode"
|
||||
#define SERVER_IP "server_ip"
|
||||
#define SERVER_PORT "server_port"
|
||||
|
||||
static 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},
|
||||
};
|
||||
|
||||
static char *get_param_str(char *name)
|
||||
{
|
||||
int idx = config_paramidx_fromname(config_parameters, sizeofArray(config_parameters), name);
|
||||
|
||||
char *param;
|
||||
if (config_parameters[idx].strptr != NULL)
|
||||
param = *config_parameters[idx].strptr;
|
||||
else
|
||||
param = NULL;
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
static int get_param_int(char *name)
|
||||
{
|
||||
int idx = config_paramidx_fromname(config_parameters, sizeofArray(config_parameters), name);
|
||||
|
||||
return *config_parameters[idx].iptr;
|
||||
}
|
||||
|
||||
void time_manager_start(time_manager_tick_function_t *_tick_functions,
|
||||
int _tick_functions_count,
|
||||
time_source_type_t time_source_type)
|
||||
{
|
||||
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;
|
||||
|
||||
tick_functions_count = _tick_functions_count;
|
||||
tick_functions = calloc_or_fail(tick_functions_count, sizeof(*tick_functions));
|
||||
memcpy(tick_functions, _tick_functions, tick_functions_count * sizeof(*tick_functions));
|
||||
|
||||
/* retrieve configuration */
|
||||
int ret = config_get(config_get_if(), config_parameters, sizeofArray(config_parameters), "time_management");
|
||||
if (ret >= 0) {
|
||||
char *param;
|
||||
|
||||
/* time source */
|
||||
param = get_param_str(TIME_SOURCE);
|
||||
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 */
|
||||
param = get_param_str(MODE);
|
||||
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 */
|
||||
param = get_param_str(SERVER_IP);
|
||||
if (param != NULL) {
|
||||
server_ip = param;
|
||||
}
|
||||
|
||||
/* server port */
|
||||
int port = get_param_int(SERVER_PORT);
|
||||
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);
|
||||
/* for the log below, there is no time source, we want to print 'server' as time source */
|
||||
time_source_type = 2;
|
||||
}
|
||||
|
||||
LOG_I(UTIL,
|
||||
"time manager configuration: [time source: %s] [mode: %s] [server IP: %s} [server port: %d]%s\n",
|
||||
(char *[]){"reatime", "iq_samples", "server"}[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)");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
free(tick_functions);
|
||||
tick_functions = NULL;
|
||||
tick_functions_count = 0;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
|
||||
#include "time_source.h"
|
||||
|
||||
typedef void (*time_manager_tick_function_t)(void);
|
||||
|
||||
void time_manager_start(time_manager_tick_function_t *tick_functions,
|
||||
int tick_functions_count,
|
||||
time_source_type_t time_source);
|
||||
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 */
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#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_or_fail(2, sizeof(struct pollfd));
|
||||
|
||||
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);
|
||||
AssertFatal(ret >= 1 || (ret == -1 && errno == EINTR), "poll() failed\n");
|
||||
|
||||
if (time_server->exit)
|
||||
break;
|
||||
|
||||
/* check tick */
|
||||
if (polls[0].revents) {
|
||||
/* do not accept errors */
|
||||
AssertFatal(polls[0].revents == POLLIN, "poll() failed\n");
|
||||
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));
|
||||
AssertFatal(polls != NULL, "realloc() failed\n");
|
||||
}
|
||||
ticks -= count;
|
||||
}
|
||||
}
|
||||
|
||||
/* check new client */
|
||||
if (polls[1].revents) {
|
||||
/* do not accept errors */
|
||||
AssertFatal(polls[1].revents == POLLIN, "poll() failed\n");
|
||||
|
||||
int new_socket = accept(time_server->server_socket, NULL, NULL);
|
||||
AssertFatal(new_socket != -1, "accept() failed: %s\n", strerror(errno));
|
||||
|
||||
/* add client */
|
||||
client_count++;
|
||||
socket_fds = realloc(socket_fds, sizeof(int) * client_count);
|
||||
AssertFatal(socket_fds != NULL, "realloc() failed\n");
|
||||
socket_fds[client_count - 1] = new_socket;
|
||||
polls = realloc(polls, sizeof(struct pollfd) * (client_count + 2));
|
||||
AssertFatal(polls != NULL, "realloc() failed\n");
|
||||
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);
|
||||
AssertFatal(socket_fds != NULL, "realloco() failed\n");
|
||||
}
|
||||
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));
|
||||
AssertFatal(polls != NULL, "realloc() failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* 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_or_fail(1, sizeof(*ts));
|
||||
|
||||
ts->server_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
AssertFatal(ts->server_socket != -1, "socket() failed: %s\n", strerror(errno));
|
||||
|
||||
int v = 1;
|
||||
ret = setsockopt(ts->server_socket, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v));
|
||||
AssertFatal(ret == 0, "setsockopt() failed: %s\n", strerror(errno));
|
||||
v = 1;
|
||||
ret = setsockopt(ts->server_socket, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v));
|
||||
AssertFatal(ret == 0, "setsockopt() failed: %s\n", strerror(errno));
|
||||
ts->tick_fd = eventfd(0, 0);
|
||||
AssertFatal(ts->tick_fd != -1, "eventfd() failed: %s\n", strerror(errno));
|
||||
|
||||
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);
|
||||
AssertFatal(ret != 0, "inet_aton() failed: %s\n", strerror(errno));
|
||||
ret = bind(ts->server_socket, (struct sockaddr *)&ip_addr, sizeof(ip_addr));
|
||||
AssertFatal(ret == 0, "bind() failed: %s\n", strerror(errno));
|
||||
|
||||
ret = listen(ts->server_socket, 10); /* 10: arbitrary value */
|
||||
AssertFatal(ret == 0, "listen() failed: %s\n", strerror(errno));
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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 */
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* 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 <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "common/utils/assertions.h"
|
||||
#include "common/utils/system.h"
|
||||
#include "common/utils/utils.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_or_fail(1, sizeof(*ts));
|
||||
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_or_fail(1, sizeof(*ts));
|
||||
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);
|
||||
}
|
||||
@@ -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 <stdint.h>
|
||||
|
||||
/* 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 */
|
||||
@@ -108,6 +108,7 @@ Legacy unmaintained files:
|
||||
- The [shared object loader](../common/utils/DOC/loader.md)
|
||||
- The [threadpool](../common/utils/threadPool/thread-pool.md) used in L1
|
||||
- The [LDPC implementation](../openair1/PHY/CODING/DOC/LDPCImplementation.md) is a shared library
|
||||
- The [time management](time_management.md) module
|
||||
|
||||
## Radios
|
||||
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
# 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 type: `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 callback functions to be called when a tick is generated
|
||||
(several callbacks can be passed) and a default time source. Based on this,
|
||||
`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_tick_function_t *tick_functions,
|
||||
int tick_functions_count,
|
||||
time_source_type_t time_source);
|
||||
```
|
||||
(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)
|
||||
```
|
||||
@@ -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,16 @@ 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)
|
||||
void nr_pdcp_ms_tick(void);
|
||||
time_manager_tick_function_t tick_functions[] = {
|
||||
nr_pdcp_ms_tick
|
||||
};
|
||||
int tick_functions_count = 1;
|
||||
time_manager_start(tick_functions, tick_functions_count, TIME_SOURCE_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 +188,8 @@ int main(int argc, char **argv)
|
||||
printf("TYPE <CTRL-C> TO TERMINATE\n");
|
||||
itti_wait_tasks_end(NULL);
|
||||
|
||||
time_manager_finish();
|
||||
|
||||
logClean();
|
||||
printf("Bye.\n");
|
||||
return 0;
|
||||
|
||||
@@ -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 <executables/softmodem-common.h>
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,38 @@ 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)
|
||||
void nr_pdcp_ms_tick(void);
|
||||
void x2ap_ms_tick();
|
||||
void nr_rlc_ms_tick(void);
|
||||
time_manager_tick_function_t tick_functions[3];
|
||||
int tick_functions_count = 0;
|
||||
if (NODE_IS_MONOLITHIC(node_type)) {
|
||||
/* monolithic */
|
||||
tick_functions[tick_functions_count++] = nr_pdcp_ms_tick;
|
||||
tick_functions[tick_functions_count++] = nr_rlc_ms_tick;
|
||||
/* x2ap is enabled when in NSA mode */
|
||||
if (get_softmodem_params()->nsa)
|
||||
tick_functions[tick_functions_count++] = x2ap_ms_tick;
|
||||
} else if (NODE_IS_CU(node_type)) {
|
||||
/* CU */
|
||||
tick_functions[tick_functions_count++] = nr_pdcp_ms_tick;
|
||||
/* x2ap is enabled when in NSA mode */
|
||||
if (get_softmodem_params()->nsa)
|
||||
tick_functions[tick_functions_count++] = x2ap_ms_tick;
|
||||
} else {
|
||||
/* DU */
|
||||
tick_functions[tick_functions_count++] = nr_rlc_ms_tick;
|
||||
}
|
||||
time_manager_start(tick_functions, tick_functions_count,
|
||||
// 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_SOURCE_IQ_SAMPLES
|
||||
: TIME_SOURCE_REALTIME);
|
||||
|
||||
// start the main threads
|
||||
number_of_cards = 1;
|
||||
|
||||
@@ -730,6 +763,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");
|
||||
|
||||
+5
-10
@@ -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;
|
||||
|
||||
@@ -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,21 @@ 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)
|
||||
void nr_pdcp_ms_tick(void);
|
||||
void nr_rlc_ms_tick(void);
|
||||
time_manager_tick_function_t tick_functions[] = {
|
||||
nr_pdcp_ms_tick,
|
||||
nr_rlc_ms_tick
|
||||
};
|
||||
int tick_functions_count = 2;
|
||||
time_manager_start(tick_functions, tick_functions_count,
|
||||
// iq_samples time source for rfsim,
|
||||
// realtime time source if not
|
||||
IS_SOFTMODEM_RFSIM ? TIME_SOURCE_IQ_SAMPLES
|
||||
: TIME_SOURCE_REALTIME);
|
||||
|
||||
if (!get_softmodem_params()->nsa && get_softmodem_params()->emulate_l1)
|
||||
start_oai_nrue_threads();
|
||||
|
||||
@@ -586,6 +602,9 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
free_nrLDPC_coding_interface(&nrLDPC_coding_interface);
|
||||
|
||||
time_manager_finish();
|
||||
|
||||
free(pckg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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_ */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_ */
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user