time management module

This commit introduces a time management module.

See doc/time_management.md for details.
This commit is contained in:
Cedric Roux
2025-04-18 09:02:54 +02:00
parent 9bccad31b5
commit 7cbedb1575
30 changed files with 1983 additions and 79 deletions
+1
View File
@@ -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})
+14
View File
@@ -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)
+285
View File
@@ -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 <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);
}
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;
}
@@ -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;
}
+231
View File
@@ -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 <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);
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);
}
+36
View File
@@ -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 */
+240
View File
@@ -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 <dlfcn.h>
#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;
}
}
+46
View File
@@ -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 <stdint.h>
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 */
+251
View File
@@ -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 <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(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);
}
+40
View File
@@ -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 */
+264
View File
@@ -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 <stdlib.h>
#include <time.h>
#include <stdbool.h>
#include <errno.h>
#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);
}
+47
View File
@@ -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 */