From 096ea7bd87f430b8bbe9d7a1e48e5ce076f44ac6 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 15 Apr 2025 09:41:53 +0200 Subject: [PATCH 01/10] Fixes for vrtsim - Allow client to exit cleanly if server is down - Ensure timing_thread is joined only once avoiding exit_function loop - Free noise_device - Save previously sent samples for channel modelling --- .../utils/shm_iq_channel/shm_td_iq_channel.c | 3 -- radio/vrtsim/noise_device.c | 4 +- radio/vrtsim/vrtsim.c | 53 +++++++++++++++---- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/common/utils/shm_iq_channel/shm_td_iq_channel.c b/common/utils/shm_iq_channel/shm_td_iq_channel.c index 89e09ddb1f..3920fe027f 100644 --- a/common/utils/shm_iq_channel/shm_td_iq_channel.c +++ b/common/utils/shm_iq_channel/shm_td_iq_channel.c @@ -228,9 +228,6 @@ IQChannelErrorType shm_td_iq_channel_rx(ShmTDIQChannel *channel, void shm_td_iq_channel_produce_samples(ShmTDIQChannel *channel, size_t num_samples) { ShmTDIQChannelData *data = channel->data; - if (channel->type != IQ_CHANNEL_TYPE_SERVER) { - return; - } if (data->is_connected == false) { return; } diff --git a/radio/vrtsim/noise_device.c b/radio/vrtsim/noise_device.c index 2e15944d4a..acb3a2b5da 100644 --- a/radio/vrtsim/noise_device.c +++ b/radio/vrtsim/noise_device.c @@ -18,6 +18,7 @@ * For more information about the OpenAirInterface (OAI) Software Alliance: * contact@openairinterface.org */ +#include "assertions.h" #include #include #include "noise_device.h" @@ -74,7 +75,8 @@ void init_noise_device(float noise_power) void free_noise_device(void) { state.running = false; - pthread_join(state.noise_device_thread, NULL); + int ret = pthread_join(state.noise_device_thread, NULL); + AssertFatal(ret == 0, "pthread_join failed: %d, errno %d (%s)\n", ret, errno, strerror(errno)); } void get_noise_vector(float *noise_vector, int length) diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index f0e0151aa6..0e602b3686 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -19,6 +19,7 @@ * contact@openairinterface.org */ +#include "PHY/TOOLS/tools_defs.h" #include #include #include @@ -50,7 +51,8 @@ // Simulator role typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; -#define NUM_CHANMOD_THREADS 1 +#define MAX_NUM_ANTENNAS_TX 4 +#define MAX_CHANNEL_LENGTH (1 << 20) #define ROLE_CLIENT_STRING "client" #define ROLE_SERVER_STRING "server" @@ -112,6 +114,9 @@ typedef struct { Actor_t *channel_modelling_actors; } vrtsim_state_t; +// Sample history for channel impulse response +static c16_t saved_samples[MAX_NUM_ANTENNAS_TX][MAX_CHANNEL_LENGTH] __attribute__((aligned(32))) = {0}; + static void histogram_add(histogram_t *histogram, double diff) { histogram->num_samples++; @@ -149,6 +154,16 @@ static void load_channel_model(vrtsim_state_t *vrtsim_state) vrtsim_state->tx_bw); char *model_name = vrtsim_state->role == ROLE_CLIENT ? "client_tx_channel_model" : "server_tx_channel_model"; vrtsim_state->channel_desc = find_channel_desc_fromname(model_name); + AssertFatal(vrtsim_state->channel_desc != NULL, + "Could not find model name %s. Make sure it is present in the config file", + model_name); + LOG_I(HW, + "Channel model %s parameters: path_loss_dB=%.2f, nb_tx=%d, nb_rx=%d, channel_length=%d\n", + model_name, + vrtsim_state->channel_desc->path_loss_dB, + vrtsim_state->channel_desc->nb_tx, + vrtsim_state->channel_desc->nb_rx, + vrtsim_state->channel_desc->channel_length); random_channel(vrtsim_state->channel_desc, 0); AssertFatal(vrtsim_state->channel_desc != NULL, "Could not find channel model %s\n", model_name); } @@ -376,7 +391,7 @@ static int vrtsim_write_internal(vrtsim_state_t *vrtsim_state, typedef struct { vrtsim_state_t *vrtsim_state; openair0_timestamp timestamp; - c16_t *samples[4]; + c16_t *samples[MAX_NUM_ANTENNAS_TX]; int nsamps; int nbAnt; int flags; @@ -410,17 +425,16 @@ static void perform_channel_modelling(void *arg) } for (int aatx = 0; aatx < nb_tx_ant; aatx++) { + c16_t *previous_samples = saved_samples[aatx]; for (int i = 0; i < nsamps; i++) { cf_t *impulse_response = channel_impulse_response[aatx]; for (int l = 0; l < channel_desc->channel_length; l++) { int idx = i - l; - // TODO: Use AVX512 for this - // TODO: What are the previously sent samples (for impulse response) - if (idx >= 0) { - c16_t tx_input = input_samples[aatx][idx]; - samples[i].r += tx_input.r * impulse_response[l].r - tx_input.i * impulse_response[l].i; - samples[i].i += tx_input.i * impulse_response[l].r + tx_input.r * impulse_response[l].i; - } + // TODO: Use AVX2 for this + c16_t tx_input = idx >= 0 ? input_samples[aatx][idx] + : previous_samples[(channel_modelling_args->timestamp + i + idx) % MAX_CHANNEL_LENGTH]; + samples[i].r += tx_input.r * impulse_response[l].r - tx_input.i * impulse_response[l].i; + samples[i].i += tx_input.i * impulse_response[l].r + tx_input.r * impulse_response[l].i; } } } @@ -462,6 +476,7 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state, int nbAnt, int flags) { + AssertFatal(nbAnt < MAX_NUM_ANTENNAS_TX, "Number of antennas %d exceeds maximum %d\n", nbAnt, MAX_NUM_ANTENNAS_TX); for (int aarx = 0; aarx < vrtsim_state->peer_info.num_rx_antennas; aarx++) { notifiedFIFO_elt_t *task = newNotifiedFIFO_elt(sizeof(channel_modelling_args_t), 0, NULL, perform_channel_modelling); channel_modelling_args_t *args = (channel_modelling_args_t *)NotifiedFifoData(task); @@ -476,6 +491,23 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state, } pushNotifiedFIFO(&vrtsim_state->channel_modelling_actors[aarx].fifo, task); } + int start_index = timestamp % MAX_CHANNEL_LENGTH; + int end_index = min(start_index + nsamps, MAX_CHANNEL_LENGTH); + int cp_nsamps = end_index - start_index; + for (int aatx = 0; aatx < nbAnt; aatx++) { + c16_t *samples = (c16_t *)samplesVoid[aatx]; + memcpy(&saved_samples[aatx][start_index], &samples[0], sizeof(c16_t) * cp_nsamps); + } + + if (end_index < start_index + nsamps) { + // wrap around condition, write at beginning of buffer + cp_nsamps = nsamps - cp_nsamps; // remaining samples + start_index = 0; + for (int aatx = 0; aatx < nbAnt; aatx++) { + c16_t *samples = (c16_t *)samplesVoid[aatx]; + memcpy(&saved_samples[aatx][start_index], &samples[0], sizeof(c16_t) * cp_nsamps); + } + } return nsamps; } @@ -506,7 +538,7 @@ static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp, static void vrtsim_end(openair0_device *device) { vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; - if (vrtsim_state->role == ROLE_SERVER) { + if (vrtsim_state->role == ROLE_SERVER && vrtsim_state->run_timing_thread) { vrtsim_state->run_timing_thread = false; int ret = pthread_join(vrtsim_state->timing_thread, NULL); AssertFatal(ret == 0, "pthread_join() failed: errno: %d, %s\n", errno, strerror(errno)); @@ -526,6 +558,7 @@ static void vrtsim_end(openair0_device *device) tx_timing->tx_samples_total += tx_timing[i].tx_samples_total; } tx_timing->average_tx_budget /= vrtsim_state->peer_info.num_rx_antennas; + free_noise_device(); } // produce 1 second of extra samples so threads can finish shm_td_iq_channel_produce_samples(vrtsim_state->channel, vrtsim_state->sample_rate); From 16f529920382a8e43b3fd4d82ddfe075289246aa Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 15 Apr 2025 16:12:13 +0200 Subject: [PATCH 02/10] Taps client client for vrtsim Added a libary that can connect to a channel emulator and provide taps as they are generated. - Enable connection from vrtsim to channel emulation server - Depends on flatbuffers definition file from raytracing-channel-emulator - Depends on libanomsg-dev and flatbuffers-compiler (Ubuntu) or equivalent - Added a compile time option ENABLE_TAPS_CLIENT to prevent other users from being affected by additional dependencies --- openair1/SIMULATION/TOOLS/sim.h | 2 + radio/vrtsim/CMakeLists.txt | 25 ++++ radio/vrtsim/taps_client.cpp | 198 ++++++++++++++++++++++++++++++++ radio/vrtsim/taps_client.h | 38 ++++++ radio/vrtsim/vrtsim.c | 70 ++++++++--- 5 files changed, 318 insertions(+), 15 deletions(-) create mode 100644 radio/vrtsim/taps_client.cpp create mode 100644 radio/vrtsim/taps_client.h diff --git a/openair1/SIMULATION/TOOLS/sim.h b/openair1/SIMULATION/TOOLS/sim.h index 98b4fb26c6..359df00b55 100644 --- a/openair1/SIMULATION/TOOLS/sim.h +++ b/openair1/SIMULATION/TOOLS/sim.h @@ -77,6 +77,8 @@ typedef struct { struct complexd **a; ///interpolated (sample-spaced) channel impulse response. size(ch) = (n_tx * n_rx) * channel_length. ATTENTION: the dimensions of ch are the transposed ones of a. This is to allow the use of BLAS when applying the correlation matrices to the state. struct complexd **ch; + ///Same as above but single precision + struct complexf **ch_ps; ///Sampled frequency response (90 kHz resolution) struct complexd **chF; ///Maximum path delay in mus. diff --git a/radio/vrtsim/CMakeLists.txt b/radio/vrtsim/CMakeLists.txt index 79eb4d88da..9ac1555099 100644 --- a/radio/vrtsim/CMakeLists.txt +++ b/radio/vrtsim/CMakeLists.txt @@ -2,3 +2,28 @@ add_library(vrtsim MODULE vrtsim.c noise_device.c) set_target_properties(vrtsim PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) target_link_libraries(vrtsim PRIVATE SIMU shm_td_iq_channel actor) add_dependencies(vrtsim generate_T) + +add_boolean_option(OAI_VRTSIM_TAPS_CLIENT OFF "Enable taps client" ON) +if (OAI_VRTSIM_TAPS_CLIENT) + target_link_libraries(vrtsim PRIVATE taps_client) + file(DOWNLOAD + "https://gitlab.eurecom.fr/oai/raytracing-channel-emulator/-/raw/59589ad2a534881810b2aa7dd81e63066245bb8d/server/api/taps.fbs?inline=false" + ${CMAKE_SOURCE_DIR}/taps.fbs + ) + + set(TAPS_IF_PATH ${CMAKE_SOURCE_DIR}/taps.fbs) + set(TAPS_API_HEADER taps_generated.h) + + add_custom_command(OUTPUT ${TAPS_API_HEADER} + COMMAND flatc -c ${TAPS_IF_PATH} + DEPENDS ${TAPS_IF_PATH} + COMMENT "Generating flatbuffers API from ${TAPS_IF_PATH}" + ) + add_library(taps_api INTERFACE) + target_include_directories(taps_api INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) + + add_library(taps_client taps_client.cpp ${TAPS_API_HEADER}) + target_include_directories(taps_client PUBLIC .) + find_package(Flatbuffers REQUIRED) + target_link_libraries(taps_client PUBLIC flatbuffers taps_api SIMU nanomsg) +endif() diff --git a/radio/vrtsim/taps_client.cpp b/radio/vrtsim/taps_client.cpp new file mode 100644 index 0000000000..6ebfc450fd --- /dev/null +++ b/radio/vrtsim/taps_client.cpp @@ -0,0 +1,198 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ +#include +#include +#include +#include +#include +#include "pthread.h" +#include "taps_generated.h" +#include "SIMULATION/TOOLS/sim.h" +extern "C" { +#include "assertions.h" +#include "common/utils/LOG/log.h" +#include "sim.h" +#include "utils.h" +} +#include + +#define NUM_TAPS_BUFFERS 4 +#define MAX_TAPS_LEN 100 +#define MAX_TAPS_MSG_SIZE (sizeof(struct complexf) * MAX_TAPS_LEN * 4 * 4 + 20) + +static pthread_t client_thread; +static bool should_run = true; +typedef struct { + int id; + int sock; + uint32_t num_tx_antennas; + uint32_t num_rx_antennas; + channel_desc_t **channel_desc; +} client_thread_args_t; + +typedef struct { + void *taps_msg; + channel_desc_t *channel_desc; +} taps_buffer_t; + +typedef struct { + taps_buffer_t taps_buffers[NUM_TAPS_BUFFERS]; + int current_buffer; +} taps_storage_t; + +void ascii_line_plot(const float *data, size_t size, char *buffer) +{ + const char levels[] = "_.-=#"; // ASCII characters for different levels + size_t num_levels = sizeof(levels) - 1; // Number of levels (excluding the null terminator) + + // Calculate min and max values from the data + float min_val = FLT_MAX; + float max_val = FLT_MIN; + for (size_t i = 0; i < size; i++) { + if (data[i] < min_val) + min_val = data[i]; + if (data[i] > max_val) + max_val = data[i]; + } + + // Handle edge case where all values are the same + if (min_val == max_val) { + min_val -= 1.0f; + max_val += 1.0f; + } + + // Normalize and map data to levels + for (size_t i = 0; i < size; i++) { + float normalized = (data[i] - min_val) / (max_val - min_val); // Normalize to [0, 1] + size_t level_index = (size_t)(normalized * num_levels); // Map to level index + if (level_index > num_levels) + level_index = num_levels; // Clamp to valid range + snprintf(buffer + i, 2, "%c", levels[level_index]); + } +} + +static void init_taps_storage(taps_storage_t *storage, int num_tx_antennas, int num_rx_antennas) +{ + for (int i = 0; i < NUM_TAPS_BUFFERS; i++) { + storage->taps_buffers[i].taps_msg = calloc_or_fail(1, MAX_TAPS_MSG_SIZE); + storage->taps_buffers[i].channel_desc = (channel_desc_t *)calloc_or_fail(1, sizeof(channel_desc_t)); + storage->taps_buffers[i].channel_desc->ch_ps = + (struct complexf **)calloc_or_fail(num_rx_antennas * num_tx_antennas, sizeof(struct complexf *)); + } + storage->current_buffer = 0; +} + +static taps_storage_t taps_storage; + +void *client_thread_func(void *args) +{ + client_thread_args_t *client_thread_args = (client_thread_args_t *)args; + while (should_run) { + int next_buffer = (taps_storage.current_buffer + 1) % NUM_TAPS_BUFFERS; + taps_buffer_t *taps_buffer = &taps_storage.taps_buffers[next_buffer]; + int ret = nn_recv(client_thread_args->sock, taps_buffer->taps_msg, MAX_TAPS_MSG_SIZE, 0); + if (ret < 0) { + LOG_E(HW, "nn_recv() failed: errno: %d, %s\n", errno, strerror(errno)); + continue; + } + auto taps_message = Phy::GetTaps(taps_buffer->taps_msg); + if (taps_message->num_rx_antennas() != client_thread_args->num_rx_antennas + || taps_message->num_tx_antennas() != client_thread_args->num_tx_antennas) { + LOG_E(HW, + "Number of antennas mismatch: expected %d x %d, got %d x %d\n", + client_thread_args->num_rx_antennas, + client_thread_args->num_tx_antennas, + taps_message->num_rx_antennas(), + taps_message->num_tx_antennas()); + continue; + } + channel_desc_t *channel_desc = taps_buffer->channel_desc; + const flatbuffers::Vector *taps = taps_message->taps(); + struct complexf *base_pointer = (struct complexf *)taps->data(); + int taps_len = taps_message->taps_len(); + AssertFatal(taps_len < MAX_TAPS_LEN, "Too many samples in the taps array, taps_len = %d\n", taps_len); + for (unsigned int aarx = 0U; aarx < client_thread_args->num_rx_antennas; aarx++) { + for (unsigned int aatx = 0U; aatx < client_thread_args->num_tx_antennas; aatx++) { + channel_desc->ch_ps[aarx + (client_thread_args->num_rx_antennas * aatx)] = + &base_pointer[(aarx + (client_thread_args->num_rx_antennas * aatx)) * taps_len]; + } + } + channel_desc->path_loss_dB = 0; + channel_desc->channel_length = taps_len; + *client_thread_args->channel_desc = channel_desc; + taps_storage.current_buffer = next_buffer; + LOG_A(HW, "Receved new taps message, channel_length %d, buffer %d\n", channel_desc->channel_length, next_buffer); + for (unsigned int aarx = 0; aarx < client_thread_args->num_rx_antennas; aarx++) { + for (unsigned int aatx = 0; aatx < client_thread_args->num_tx_antennas; aatx++) { + char buffer[MAX_TAPS_LEN + 1]; + memset(buffer, 0, sizeof(buffer)); + float magnitudes[MAX_TAPS_LEN]; + cf_t *channel = channel_desc->ch_ps[aarx + (client_thread_args->num_rx_antennas * aatx)]; + for (int i = 0; i < channel_desc->channel_length; i++) { + magnitudes[i] = sqrtf(powf(channel[i].r, 2) + powf(channel[i].i, 2)); + } + ascii_line_plot(magnitudes, channel_desc->channel_length, buffer); + LOG_A(HW, "Taps message %d, channel %d x %d: %s\n", client_thread_args->id, aarx, aatx, buffer); + } + } + } + return NULL; +} + +extern "C" void taps_client_connect(int id, + const char *socket_path, + int num_tx_antennas, + int num_rx_antennas, + channel_desc_t **channel_desc) +{ + // Create a socket + int sock = nn_socket(AF_SP, NN_SUB); + AssertFatal(sock >= 0, "nn_socket() failed: errno: %d, %s\n", errno, strerror(errno)); + + int ret = nn_connect(sock, socket_path); + AssertFatal(ret >= 0, "nn_connect() failed: errno: %d, %s\n", errno, strerror(errno)); + + // Subscribe to all messages + ret = nn_setsockopt(sock, NN_SUB, NN_SUB_SUBSCRIBE, "", 0); + AssertFatal(ret == 0, "nn_setsockopt() failed, errno %d, %s\n", errno, strerror(errno)); + + init_taps_storage(&taps_storage, num_tx_antennas, num_rx_antennas); + + client_thread_args_t *client_thread_args = static_cast(malloc(sizeof(client_thread_args_t))); + client_thread_args->id = id; + client_thread_args->sock = sock; + client_thread_args->num_rx_antennas = num_rx_antennas; + client_thread_args->num_tx_antennas = num_tx_antennas; + client_thread_args->channel_desc = channel_desc; + ret = pthread_create(&client_thread, NULL, client_thread_func, client_thread_args); + AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno)); +} + +extern "C" void taps_client_stop() +{ + should_run = false; + pthread_join(client_thread, NULL); + for (int i = 0; i < NUM_TAPS_BUFFERS; i++) { + free(taps_storage.taps_buffers[i].taps_msg); + free(taps_storage.taps_buffers[i].channel_desc->ch_ps); + free(taps_storage.taps_buffers[i].channel_desc); + } +} diff --git a/radio/vrtsim/taps_client.h b/radio/vrtsim/taps_client.h new file mode 100644 index 0000000000..c0a577c787 --- /dev/null +++ b/radio/vrtsim/taps_client.h @@ -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 TAPS_CLIENT_H +#define TAPS_CLIENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sim.h" + +void taps_client_connect(int id, const char *socket_path, int num_tx_antennas, int num_rx_antennas, channel_desc_t **channel_desc); +void taps_client_stop(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index 0e602b3686..9d498d450b 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -47,6 +47,7 @@ #include "actor.h" #include "noise_device.h" #include "simde/x86/avx512.h" +#include "taps_client.h" // Simulator role typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; @@ -60,6 +61,7 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; #define VRTSIM_SECTION "vrtsim" #define TIME_SCALE_HLP \ "sample time scale. 1.0 means realtime. Values > 1 mean faster than realtime. Values < 1 mean slower than realtime\n" +#define TAPS_SOCKET_HLP "Socket to connect to the channel emulation server\n" // clang-format off #define VRTSIM_PARAMS_DESC \ @@ -68,6 +70,7 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; {"role", "either client or server\n", 0, .strptr = &role, .defstrval = ROLE_CLIENT_STRING, TYPE_STRING, 0}, \ {"timescale", TIME_SCALE_HLP, 0, .dblptr = &vrtsim_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \ {"chanmod", "Enable channel modelling", 0, .iptr = &vrtsim_state->chanmod, .defintval = 0, TYPE_INT, 0}, \ + {"taps-socket", TAPS_SOCKET_HLP, 0, .strptr = &vrtsim_state->taps_socket, .defstrval = NULL, TYPE_STRING, 0}, \ }; // clang-format on @@ -112,6 +115,7 @@ typedef struct { int rx_num_channels; channel_desc_t *channel_desc; Actor_t *channel_modelling_actors; + char *taps_socket; } vrtsim_state_t; // Sample history for channel impulse response @@ -183,6 +187,15 @@ static void vrtsim_readconfig(vrtsim_state_t *vrtsim_state) } else { AssertFatal(false, "Invalid role configuration\n"); } +#ifdef ENABLE_TAPS_CLIENT + if (vrtsim_state->taps_socket) { + LOG_A(HW, "VRTSIM: will use taps socket %s\n", vrtsim_state->taps_socket); + } +#else + if (vrtsim_state->taps_socket) { + AssertFatal(false, "Invalid configuration: Build with ENABLE_TAPS_CLIENT to use taps socket\n"); + } +#endif } static void *vrtsim_timing_job(void *arg) @@ -326,12 +339,20 @@ static int vrtsim_connect(openair0_device *device) // Handle channel modelling after number of RX antennas are known int num_tx_stats = 1; - if (vrtsim_state->chanmod) { + if (vrtsim_state->chanmod || vrtsim_state->taps_socket) { vrtsim_state->channel_modelling_actors = calloc_or_fail(vrtsim_state->peer_info.num_rx_antennas, sizeof(Actor_t)); for (int i = 0; i < vrtsim_state->peer_info.num_rx_antennas; i++) { init_actor(&vrtsim_state->channel_modelling_actors[i], "chanmod", -1); } - load_channel_model(vrtsim_state); + if (vrtsim_state->taps_socket) { + taps_client_connect(0, + vrtsim_state->taps_socket, + device->openair0_cfg[0].tx_num_channels, + vrtsim_state->peer_info.num_rx_antennas, + &vrtsim_state->channel_desc); + } else { + load_channel_model(vrtsim_state); + } num_tx_stats = vrtsim_state->peer_info.num_rx_antennas; } vrtsim_state->tx_timing = calloc_or_fail(num_tx_stats, sizeof(tx_timing_t)); @@ -401,6 +422,7 @@ typedef struct { static void perform_channel_modelling(void *arg) { channel_modelling_args_t *channel_modelling_args = arg; + vrtsim_state_t *vrtsim_state = channel_modelling_args->vrtsim_state; int nsamps = channel_modelling_args->nsamps; int aarx = channel_modelling_args->aarx; int nb_tx_ant = channel_modelling_args->nbAnt; @@ -411,23 +433,36 @@ static void perform_channel_modelling(void *arg) // Apply noise from global settings get_noise_vector((float *)samples, nsamps * 2); - channel_desc_t *channel_desc = channel_modelling_args->vrtsim_state->channel_desc; - const float pathloss_linear = powf(10, channel_desc->path_loss_dB / 20.0); + channel_desc_t *channel_desc = vrtsim_state->channel_desc; + + if (channel_desc == NULL) { + return; + } - // Convert channel impulse response to float + apply pathloss cf_t channel_impulse_response[nb_tx_ant][channel_desc->channel_length]; - for (int aatx = 0; aatx < nb_tx_ant; aatx++) { - const struct complexd *channelModel = channel_desc->ch[aarx + (aatx * channel_desc->nb_rx)]; - for (int i = 0; i < channel_desc->channel_length; i++) { - channel_impulse_response[aatx][i].r = channelModel[i].r * pathloss_linear; - channel_impulse_response[aatx][i].i = channelModel[i].i * pathloss_linear; + cf_t *channel_impulse_response_p[nb_tx_ant]; + if (!vrtsim_state->taps_socket) { + const float pathloss_linear = powf(10, channel_desc->path_loss_dB / 20.0); + // Convert channel impulse response to float + apply pathloss + for (int aatx = 0; aatx < nb_tx_ant; aatx++) { + const struct complexd *channelModel = channel_desc->ch[aarx + (aatx * channel_desc->nb_rx)]; + for (int i = 0; i < channel_desc->channel_length; i++) { + channel_impulse_response[aatx][i].r = channelModel[i].r * pathloss_linear; + channel_impulse_response[aatx][i].i = channelModel[i].i * pathloss_linear; + } + channel_impulse_response_p[aatx] = channel_impulse_response[aatx]; + } + } else { + for (int aatx = 0; aatx < nb_tx_ant; aatx++) { + struct complexf *channelModel = channel_desc->ch_ps[aarx + (aatx * channel_desc->nb_rx)]; + channel_impulse_response_p[aatx] = channelModel; } } for (int aatx = 0; aatx < nb_tx_ant; aatx++) { c16_t *previous_samples = saved_samples[aatx]; for (int i = 0; i < nsamps; i++) { - cf_t *impulse_response = channel_impulse_response[aatx]; + cf_t *impulse_response = channel_impulse_response_p[aatx]; for (int l = 0; l < channel_desc->channel_length; l++) { int idx = i - l; // TODO: Use AVX2 for this @@ -515,8 +550,9 @@ static int vrtsim_write(openair0_device *device, openair0_timestamp timestamp, v { timestamp -= device->openair0_cfg->command_line_sample_advance; vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; - return vrtsim_state->chanmod ? vrtsim_write_with_chanmod(vrtsim_state, timestamp, samplesVoid, nsamps, nbAnt, flags) - : vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0); + bool channel_modelling = vrtsim_state->chanmod || vrtsim_state->taps_socket; + return channel_modelling ? vrtsim_write_with_chanmod(vrtsim_state, timestamp, samplesVoid, nsamps, nbAnt, flags) + : vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0); } static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) @@ -545,7 +581,7 @@ static void vrtsim_end(openair0_device *device) } tx_timing_t *tx_timing = vrtsim_state->tx_timing; - if (vrtsim_state->chanmod) { + if (vrtsim_state->chanmod || vrtsim_state->taps_socket) { for (int i = 0; i < vrtsim_state->peer_info.num_rx_antennas; i++) { shutdown_actor(&vrtsim_state->channel_modelling_actors[i]); } @@ -559,6 +595,9 @@ static void vrtsim_end(openair0_device *device) } tx_timing->average_tx_budget /= vrtsim_state->peer_info.num_rx_antennas; free_noise_device(); + if (vrtsim_state->taps_socket) { + taps_client_stop(); + } } // produce 1 second of extra samples so threads can finish shm_td_iq_channel_produce_samples(vrtsim_state->channel, vrtsim_state->sample_rate); @@ -621,10 +660,11 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device *devi vrtsim_state->tx_num_channels = openair0_cfg->tx_num_channels; vrtsim_state->rx_num_channels = openair0_cfg->rx_num_channels; - if (vrtsim_state->chanmod) { + if (vrtsim_state->chanmod || vrtsim_state->taps_socket) { init_channelmod(); int noise_power_dBFS = get_noise_power_dBFS(); int16_t noise_power = noise_power_dBFS == INVALID_DBFS_VALUE ? 0 : (int16_t)(32767.0 / powf(10.0, .05 * -noise_power_dBFS)); + LOG_A(HW, "VRTSIM: Noise power %d sample value\n", noise_power); init_noise_device(noise_power); } return 0; From fcea07ca715aae57efb7b600c44eeeeb1bad0b47 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 27 Jun 2025 10:46:53 +0200 Subject: [PATCH 03/10] Detect disconnecton & handle error states in vrtsim Add handling for error states in vrtsim which allows to close the softmodems with CTRL+C without hanging, improving user experience - Client detection of stale timer. - Abort state for client and server --- .../utils/shm_iq_channel/shm_td_iq_channel.c | 63 ++++++++++++++++--- .../utils/shm_iq_channel/shm_td_iq_channel.h | 19 +++++- .../tests/test_shm_td_iq_channel.c | 4 +- radio/vrtsim/vrtsim.c | 13 +++- 4 files changed, 85 insertions(+), 14 deletions(-) diff --git a/common/utils/shm_iq_channel/shm_td_iq_channel.c b/common/utils/shm_iq_channel/shm_td_iq_channel.c index 3920fe027f..1086979a75 100644 --- a/common/utils/shm_iq_channel/shm_td_iq_channel.c +++ b/common/utils/shm_iq_channel/shm_td_iq_channel.c @@ -52,6 +52,7 @@ typedef struct ShmTDIQChannel_s { char name[256]; sample_t *tx_iq_data; sample_t *rx_iq_data; + bool abort; } ShmTDIQChannel; ShmTDIQChannel *shm_td_iq_channel_create(const char *name, int num_tx_ant, int num_rx_ant) @@ -237,10 +238,11 @@ void shm_td_iq_channel_produce_samples(ShmTDIQChannel *channel, size_t num_sampl mutexunlock(data->mutex); } -void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp) +void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_t timeout_uS) { ShmTDIQChannelData *data = channel->data; if (data->is_connected == false) { + fprintf(stderr, "Error: Channel is not connected.\n"); abort(); return; } @@ -249,13 +251,41 @@ void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp) if (current_timestamp >= timestamp) { return; } - mutexlock(data->mutex); - while (current_timestamp < timestamp) { - condwait(data->cond, data->mutex); - current_timestamp = data->timestamp; + if (timeout_uS == 0) { + mutexlock(data->mutex); + while (current_timestamp < timestamp && !channel->abort) { + condwait(data->cond, data->mutex); + current_timestamp = data->timestamp; + } + mutexunlock(data->mutex); + } else { + struct timespec ts = {.tv_sec = 0, .tv_nsec = 0}; + if (clock_gettime(CLOCK_REALTIME, &ts) != 0) { + fprintf(stderr, "Error: clock_gettime failed: %s\n", strerror(errno)); + channel->abort = true; + return; + } + + ts.tv_sec += timeout_uS / 1000000; // Convert microseconds to seconds + ts.tv_nsec += (timeout_uS % 1000000) * 1000; // Convert remaining microseconds to nanoseconds + + mutexlock(data->mutex); + while (current_timestamp < timestamp && !channel->abort) { + int ret = pthread_cond_timedwait(&data->cond, &data->mutex, &ts); + if (ret == ETIMEDOUT) { + channel->abort = true; + fprintf(stderr, "Error: Timed out waiting for samples. Aborting vrtsim\n");\ + break; + } else if (ret != 0) { + channel->abort = true; + fprintf(stderr, "Error: pthread_cond_timedwait failed: %s\n", strerror(ret)); + break; + } else { + current_timestamp = data->timestamp; + } + } + mutexunlock(data->mutex); } - mutexunlock(data->mutex); - return; } uint64_t shm_td_iq_channel_get_current_sample(const ShmTDIQChannel *channel) @@ -269,15 +299,32 @@ bool shm_td_iq_channel_is_connected(const ShmTDIQChannel *channel) return channel->data->is_connected; } +void shm_td_iq_channel_abort(ShmTDIQChannel *channel) +{ + ShmTDIQChannelData *data = channel->data; + mutexlock(data->mutex); + channel->abort = true; + condbroadcast(data->cond); + mutexunlock(data->mutex); +} + +bool shm_td_iq_channel_is_aborted(const ShmTDIQChannel *channel) +{ + return channel->abort; +} + void shm_td_iq_channel_destroy(ShmTDIQChannel *channel) { ShmTDIQChannelData *data = channel->data; size_t tx_buffer_size = CIRCULAR_BUFFER_SIZE * sizeof(sample_t) * data->num_antennas_tx; size_t rx_buffer_size = CIRCULAR_BUFFER_SIZE * sizeof(sample_t) * data->num_antennas_rx; size_t total_size = sizeof(ShmTDIQChannelData) + tx_buffer_size + rx_buffer_size; - munmap(data, total_size); if (channel->type == IQ_CHANNEL_TYPE_SERVER) { + data->is_connected = false; + munmap(data, total_size); shm_unlink(channel->name); + } else { + munmap(data, total_size); } free(channel); } diff --git a/common/utils/shm_iq_channel/shm_td_iq_channel.h b/common/utils/shm_iq_channel/shm_td_iq_channel.h index 7c5a730914..30c7cd73f1 100644 --- a/common/utils/shm_iq_channel/shm_td_iq_channel.h +++ b/common/utils/shm_iq_channel/shm_td_iq_channel.h @@ -118,8 +118,10 @@ void shm_td_iq_channel_produce_samples(ShmTDIQChannel *channel, uint64_t num_sam * @brief Wait until sample at the specified timestamp is available * * @param channel The ShmTDIQChannel structure. + * @param timestamp The timestamp for which to wait. + * @param timeout_uS The timeout in microseconds to wait for the sample. 0 means wait indefinitely. */ -void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp); +void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_t timeout_uS); /** * @brief Checks if the IQ channel is connected. @@ -129,6 +131,21 @@ void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp); */ bool shm_td_iq_channel_is_connected(const ShmTDIQChannel *channel); +/** + * @brief Aborts the IQ channel causing the wait to return immediately + * + * @param channel The ShmTDIQChannel structure. + */ +void shm_td_iq_channel_abort(ShmTDIQChannel *channel); + +/** + * @brief Checks if the IQ channel is aborted. + * + * @param channel The ShmTDIQChannel structure. + * @return True if the channel is aborted, false otherwise. + */ +bool shm_td_iq_channel_is_aborted(const ShmTDIQChannel *channel); + /** * @brief Destroys the shared memory IQ channel. * diff --git a/common/utils/shm_iq_channel/tests/test_shm_td_iq_channel.c b/common/utils/shm_iq_channel/tests/test_shm_td_iq_channel.c index f5170b8144..351bdd678d 100644 --- a/common/utils/shm_iq_channel/tests/test_shm_td_iq_channel.c +++ b/common/utils/shm_iq_channel/tests/test_shm_td_iq_channel.c @@ -104,7 +104,7 @@ void server(void) uint64_t timestamp = 0; int iq_contents = 0; while (timestamp < num_samples_per_update * num_updates) { - shm_td_iq_channel_wait(channel, timestamp + num_samples_per_update); + shm_td_iq_channel_wait(channel, timestamp + num_samples_per_update, 0); uint64_t target_timestamp = timestamp + num_samples_per_update; timestamp += num_samples_per_update; uint32_t iq_data[num_samples_per_update]; @@ -141,7 +141,7 @@ int client(void) int iq_contents = 0; while (timestamp < num_samples_per_update * num_updates) { - shm_td_iq_channel_wait(channel, timestamp + num_samples_per_update); + shm_td_iq_channel_wait(channel, timestamp + num_samples_per_update, 0); // Server starts producing from second slot if (timestamp > num_samples_per_update) { uint64_t target_timestamp = timestamp - num_samples_per_update; diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index 9d498d450b..7fb85589ea 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -558,7 +558,15 @@ static int vrtsim_write(openair0_device *device, openair0_timestamp timestamp, v static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) { vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; - shm_td_iq_channel_wait(vrtsim_state->channel, vrtsim_state->last_received_sample + nsamps); + if (shm_td_iq_channel_is_aborted(vrtsim_state->channel)) { + LOG_E(HW, "Channel is aborted, returning void samples\n"); + for (int i = 0; i < nbAnt; i++) { + memset(samplesVoid[i], 0, sizeof(c16_t) * nsamps); + } + return nsamps; + } + uint64_t timeout_uS = 2 * 1000 * 1000 / vrtsim_state->timescale; // 2 seconds in uS + shm_td_iq_channel_wait(vrtsim_state->channel, vrtsim_state->last_received_sample + nsamps, timeout_uS); int ret = shm_td_iq_channel_rx(vrtsim_state->channel, vrtsim_state->last_received_sample, nsamps, 0, samplesVoid[0]); if (ret == CHANNEL_ERROR_TOO_LATE) { vrtsim_state->rx_samples_late += nsamps; @@ -599,8 +607,7 @@ static void vrtsim_end(openair0_device *device) taps_client_stop(); } } - // produce 1 second of extra samples so threads can finish - shm_td_iq_channel_produce_samples(vrtsim_state->channel, vrtsim_state->sample_rate); + shm_td_iq_channel_abort(vrtsim_state->channel); sleep(1); shm_td_iq_channel_destroy(vrtsim_state->channel); From acba82b874930b00a623f14b3f34c3a528f10592 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 7 Aug 2025 14:03:05 +0200 Subject: [PATCH 04/10] Simplify shm td iq channel implementation Move the channel state management outside the channel implementation. This simplifies the channel implementation but forces users of the channel to manage it's state manually. --- .../utils/shm_iq_channel/shm_td_iq_channel.c | 27 ------------------- .../utils/shm_iq_channel/shm_td_iq_channel.h | 8 ------ .../tests/test_shm_td_iq_channel.c | 18 ------------- radio/vrtsim/vrtsim.c | 4 --- 4 files changed, 57 deletions(-) diff --git a/common/utils/shm_iq_channel/shm_td_iq_channel.c b/common/utils/shm_iq_channel/shm_td_iq_channel.c index 1086979a75..126c386be9 100644 --- a/common/utils/shm_iq_channel/shm_td_iq_channel.c +++ b/common/utils/shm_iq_channel/shm_td_iq_channel.c @@ -40,7 +40,6 @@ typedef struct { int num_antennas_tx; int num_antennas_rx; uint64_t timestamp; - bool is_connected; pthread_mutex_t mutex; pthread_cond_t cond; } ShmTDIQChannelData; @@ -48,7 +47,6 @@ typedef struct { typedef struct ShmTDIQChannel_s { IQChannelType type; ShmTDIQChannelData *data; - uint64_t last_timestamp; char name[256]; sample_t *tx_iq_data; sample_t *rx_iq_data; @@ -76,14 +74,12 @@ ShmTDIQChannel *shm_td_iq_channel_create(const char *name, int num_tx_ant, int n memset(shm_ptr, 0, total_size); shm_ptr->num_antennas_tx = num_tx_ant; shm_ptr->num_antennas_rx = num_rx_ant; - shm_ptr->is_connected = false; ShmTDIQChannel *channel = calloc_or_fail(1, sizeof(ShmTDIQChannel)); strncpy(channel->name, name, sizeof(channel->name) - 1); channel->tx_iq_data = (sample_t *)(shm_ptr + 1); channel->rx_iq_data = channel->tx_iq_data + tx_buffer_size / sizeof(sample_t); channel->data = shm_ptr; channel->type = IQ_CHANNEL_TYPE_SERVER; - channel->last_timestamp = 0; pthread_mutexattr_t mutex_attr; pthread_condattr_t cond_attr; int ret = pthread_mutexattr_init(&mutex_attr); @@ -138,12 +134,10 @@ ShmTDIQChannel *shm_td_iq_channel_connect(const char *name, int timeout_in_secon size_t tx_buffer_size = CIRCULAR_BUFFER_SIZE * sizeof(sample_t) * channel->data->num_antennas_tx; channel->rx_iq_data = channel->tx_iq_data + tx_buffer_size / sizeof(sample_t); channel->type = IQ_CHANNEL_TYPE_CLIENT; - channel->last_timestamp = 0; while (shm_ptr->magic != SHM_MAGIC_NUMBER) { printf("Waiting for server to initialize shared memory\n"); sleep(1); } - shm_ptr->is_connected = true; close(fd); return channel; } @@ -155,9 +149,6 @@ IQChannelErrorType shm_td_iq_channel_tx(ShmTDIQChannel *channel, const sample_t *tx_iq_data) { ShmTDIQChannelData *data = channel->data; - if (data->is_connected == false) { - return CHANNEL_ERROR_NOT_CONNECTED; - } // timestamp in the past uint64_t current_time = data->timestamp; if (timestamp < current_time) { @@ -194,9 +185,6 @@ IQChannelErrorType shm_td_iq_channel_rx(ShmTDIQChannel *channel, sample_t *tx_iq_data) { ShmTDIQChannelData *data = channel->data; - if (data->is_connected == false) { - return CHANNEL_ERROR_NOT_CONNECTED; - } // timestamp in the future uint64_t current_time = data->timestamp; if (timestamp > current_time) { @@ -229,9 +217,6 @@ IQChannelErrorType shm_td_iq_channel_rx(ShmTDIQChannel *channel, void shm_td_iq_channel_produce_samples(ShmTDIQChannel *channel, size_t num_samples) { ShmTDIQChannelData *data = channel->data; - if (data->is_connected == false) { - return; - } mutexlock(data->mutex); data->timestamp += num_samples; condbroadcast(data->cond); @@ -241,12 +226,6 @@ void shm_td_iq_channel_produce_samples(ShmTDIQChannel *channel, size_t num_sampl void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_t timeout_uS) { ShmTDIQChannelData *data = channel->data; - if (data->is_connected == false) { - fprintf(stderr, "Error: Channel is not connected.\n"); - abort(); - return; - } - size_t current_timestamp = data->timestamp; if (current_timestamp >= timestamp) { return; @@ -294,11 +273,6 @@ uint64_t shm_td_iq_channel_get_current_sample(const ShmTDIQChannel *channel) return data->timestamp; } -bool shm_td_iq_channel_is_connected(const ShmTDIQChannel *channel) -{ - return channel->data->is_connected; -} - void shm_td_iq_channel_abort(ShmTDIQChannel *channel) { ShmTDIQChannelData *data = channel->data; @@ -320,7 +294,6 @@ void shm_td_iq_channel_destroy(ShmTDIQChannel *channel) size_t rx_buffer_size = CIRCULAR_BUFFER_SIZE * sizeof(sample_t) * data->num_antennas_rx; size_t total_size = sizeof(ShmTDIQChannelData) + tx_buffer_size + rx_buffer_size; if (channel->type == IQ_CHANNEL_TYPE_SERVER) { - data->is_connected = false; munmap(data, total_size); shm_unlink(channel->name); } else { diff --git a/common/utils/shm_iq_channel/shm_td_iq_channel.h b/common/utils/shm_iq_channel/shm_td_iq_channel.h index 30c7cd73f1..8c7bfad318 100644 --- a/common/utils/shm_iq_channel/shm_td_iq_channel.h +++ b/common/utils/shm_iq_channel/shm_td_iq_channel.h @@ -123,14 +123,6 @@ void shm_td_iq_channel_produce_samples(ShmTDIQChannel *channel, uint64_t num_sam */ void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_t timeout_uS); -/** - * @brief Checks if the IQ channel is connected. - * - * @param channel The ShmTDIQChannel structure. - * @return True if the channel is connected, false otherwise. - */ -bool shm_td_iq_channel_is_connected(const ShmTDIQChannel *channel); - /** * @brief Aborts the IQ channel causing the wait to return immediately * diff --git a/common/utils/shm_iq_channel/tests/test_shm_td_iq_channel.c b/common/utils/shm_iq_channel/tests/test_shm_td_iq_channel.c index 351bdd678d..05140a0f63 100644 --- a/common/utils/shm_iq_channel/tests/test_shm_td_iq_channel.c +++ b/common/utils/shm_iq_channel/tests/test_shm_td_iq_channel.c @@ -87,15 +87,6 @@ void server(void) int num_ant_tx = 1; int num_ant_rx = 1; ShmTDIQChannel *channel = shm_td_iq_channel_create(SHM_CHANNEL_NAME, num_ant_tx, num_ant_rx); - for (int i = 0; i < 10; i++) { - if (shm_td_iq_channel_is_connected(channel)) { - printf("Server connected\n"); - break; - } - printf("Waiting for client\n"); - sleep(1); - } - AssertFatal(shm_td_iq_channel_is_connected(channel), "Server failed to connect\n"); pthread_t producer_thread; int ret = pthread_create(&producer_thread, NULL, produce_symbols, channel); @@ -127,15 +118,6 @@ int client(void) { int total_errors = 0; ShmTDIQChannel *channel = shm_td_iq_channel_connect(SHM_CHANNEL_NAME, 10); - for (int i = 0; i < 10; i++) { - if (shm_td_iq_channel_is_connected(channel)) { - printf("Client connected\n"); - break; - } - printf("Waiting for server\n"); - sleep(1); - } - AssertFatal(shm_td_iq_channel_is_connected(channel), "Client failed to connect\n"); uint64_t timestamp = 0; int iq_contents = 0; diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index 7fb85589ea..c668bac64c 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -368,10 +368,6 @@ static int vrtsim_connect(openair0_device *device) vrtsim_state->peer_info.num_rx_antennas, device->openair0_cfg[0].rx_num_channels); vrtsim_state->run_timing_thread = true; - while (!shm_td_iq_channel_is_connected(vrtsim_state->channel)) { - LOG_I(HW, "Waiting for client\n"); - sleep(1); - } int ret = pthread_create(&vrtsim_state->timing_thread, NULL, vrtsim_timing_job, vrtsim_state); AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno)); } else { From fc28e2d3fc6220325e35a1b9d4c4cae8892c7ec3 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 7 Aug 2025 14:05:06 +0200 Subject: [PATCH 05/10] Make vrtsim output to stdout more readable. --- radio/vrtsim/vrtsim.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index c668bac64c..f5d2410bf1 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -555,7 +555,6 @@ static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp, { vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; if (shm_td_iq_channel_is_aborted(vrtsim_state->channel)) { - LOG_E(HW, "Channel is aborted, returning void samples\n"); for (int i = 0; i < nbAnt; i++) { memset(samplesVoid[i], 0, sizeof(c16_t) * nsamps); } @@ -615,7 +614,7 @@ static void vrtsim_end(openair0_device *device) "VRTSIM: Read/write too early (suspected radio implementaton error) TX: %lu, RX: %lu\n", tx_timing->tx_early, vrtsim_state->rx_early); - LOG_I(HW, "VRTSIM: Average TX budget %.3lf uS\n", tx_timing->average_tx_budget); + LOG_I(HW, "VRTSIM: Average TX budget %.3lf uS (more is better)\n", tx_timing->average_tx_budget); histogram_print(&tx_timing->tx_histogram); free(vrtsim_state->tx_timing); } From 54fdd3547bc8125721b3b0520a7b433f614d7625 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 8 Aug 2025 16:51:22 +0200 Subject: [PATCH 06/10] Clean exit of vrtsim client if server is stopped first Force exit in case current sample is not modified by the server with 2s. --- .../utils/shm_iq_channel/shm_td_iq_channel.c | 18 +++++----- .../utils/shm_iq_channel/shm_td_iq_channel.h | 4 ++- radio/vrtsim/vrtsim.c | 35 +++++++++++++++---- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/common/utils/shm_iq_channel/shm_td_iq_channel.c b/common/utils/shm_iq_channel/shm_td_iq_channel.c index 126c386be9..ecebbaf494 100644 --- a/common/utils/shm_iq_channel/shm_td_iq_channel.c +++ b/common/utils/shm_iq_channel/shm_td_iq_channel.c @@ -223,12 +223,12 @@ void shm_td_iq_channel_produce_samples(ShmTDIQChannel *channel, size_t num_sampl mutexunlock(data->mutex); } -void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_t timeout_uS) +int shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_t timeout_uS) { ShmTDIQChannelData *data = channel->data; size_t current_timestamp = data->timestamp; if (current_timestamp >= timestamp) { - return; + return 0; } if (timeout_uS == 0) { mutexlock(data->mutex); @@ -241,8 +241,7 @@ void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_ struct timespec ts = {.tv_sec = 0, .tv_nsec = 0}; if (clock_gettime(CLOCK_REALTIME, &ts) != 0) { fprintf(stderr, "Error: clock_gettime failed: %s\n", strerror(errno)); - channel->abort = true; - return; + return 1; } ts.tv_sec += timeout_uS / 1000000; // Convert microseconds to seconds @@ -252,19 +251,20 @@ void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_ while (current_timestamp < timestamp && !channel->abort) { int ret = pthread_cond_timedwait(&data->cond, &data->mutex, &ts); if (ret == ETIMEDOUT) { - channel->abort = true; - fprintf(stderr, "Error: Timed out waiting for samples. Aborting vrtsim\n");\ - break; + fprintf(stderr, "Error: Timed out waiting for samples.\n"); + mutexunlock(data->mutex); + return 1; } else if (ret != 0) { - channel->abort = true; fprintf(stderr, "Error: pthread_cond_timedwait failed: %s\n", strerror(ret)); - break; + mutexunlock(data->mutex); + return 1; } else { current_timestamp = data->timestamp; } } mutexunlock(data->mutex); } + return 0; } uint64_t shm_td_iq_channel_get_current_sample(const ShmTDIQChannel *channel) diff --git a/common/utils/shm_iq_channel/shm_td_iq_channel.h b/common/utils/shm_iq_channel/shm_td_iq_channel.h index 8c7bfad318..b71720c1f9 100644 --- a/common/utils/shm_iq_channel/shm_td_iq_channel.h +++ b/common/utils/shm_iq_channel/shm_td_iq_channel.h @@ -120,8 +120,10 @@ void shm_td_iq_channel_produce_samples(ShmTDIQChannel *channel, uint64_t num_sam * @param channel The ShmTDIQChannel structure. * @param timestamp The timestamp for which to wait. * @param timeout_uS The timeout in microseconds to wait for the sample. 0 means wait indefinitely. + * + * @return 0 if the sample is available, 1 if timed out */ -void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_t timeout_uS); +int shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_t timeout_uS); /** * @brief Aborts the IQ channel causing the wait to return immediately diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index f5d2410bf1..97c820a859 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -223,6 +223,7 @@ static void *vrtsim_timing_job(void *arg) samples_to_produce += 1; leftover_samples -= 1; } + AssertFatal(samples_to_produce >= 0, "Negative samples to produce: %f\n", samples_to_produce); shm_td_iq_channel_produce_samples(vrtsim_state->channel, samples_to_produce); usleep(1); } @@ -544,6 +545,12 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state, static int vrtsim_write(openair0_device *device, openair0_timestamp timestamp, void **samplesVoid, int nsamps, int nbAnt, int flags) { + AssertFatal(nsamps > 0, "Number of samples must be greater than 0\n"); + AssertFatal(nbAnt > 0 && nbAnt <= MAX_NUM_ANTENNAS_TX, + "Number of antennas %d must be between 1 and %d\n", + nbAnt, + MAX_NUM_ANTENNAS_TX); + AssertFatal(timestamp >= 0, "Timestamp must be non-negative, got %ld\n", timestamp); timestamp -= device->openair0_cfg->command_line_sample_advance; vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; bool channel_modelling = vrtsim_state->chanmod || vrtsim_state->taps_socket; @@ -555,13 +562,29 @@ static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp, { vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; if (shm_td_iq_channel_is_aborted(vrtsim_state->channel)) { - for (int i = 0; i < nbAnt; i++) { - memset(samplesVoid[i], 0, sizeof(c16_t) * nsamps); - } - return nsamps; + return 0; } - uint64_t timeout_uS = 2 * 1000 * 1000 / vrtsim_state->timescale; // 2 seconds in uS - shm_td_iq_channel_wait(vrtsim_state->channel, vrtsim_state->last_received_sample + nsamps, timeout_uS); + if (vrtsim_state->role == ROLE_SERVER) { + uint64_t timeout_uS = 0; // 0 means no timeout + shm_td_iq_channel_wait(vrtsim_state->channel, vrtsim_state->last_received_sample + nsamps, timeout_uS); + } else { + uint64_t start_sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel); + uint64_t timeout_uS = 2 * 1000 * 1000; // 2 seconds timeout waiting for sample number to change + // + while (shm_td_iq_channel_wait(vrtsim_state->channel, vrtsim_state->last_received_sample + nsamps, timeout_uS) == 1) { + uint64_t sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel); + if (sample == start_sample) { + LOG_E(HW, + "VRTSIM: Read timeout waiting for sample %lu to change, aborting channel\n", + vrtsim_state->last_received_sample + nsamps); + shm_td_iq_channel_abort(vrtsim_state->channel); + break; + } else { + start_sample = sample; + } + } + } + int ret = shm_td_iq_channel_rx(vrtsim_state->channel, vrtsim_state->last_received_sample, nsamps, 0, samplesVoid[0]); if (ret == CHANNEL_ERROR_TOO_LATE) { vrtsim_state->rx_samples_late += nsamps; From ea3a4b991c33be95c19b8e3e0b978650d8e677a0 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 8 Aug 2025 17:24:40 +0200 Subject: [PATCH 07/10] VRTSIM: allow client reconnection via a connection descriptor file Replace the unwieldy unix socket implementation of peer information exchange with an authorative connection descriptor file that informs the client of the connection method an number of antennas on the server. This allows the server to run without the client and allows the client to reconnect as long as the server is running, as many times as needed. --- radio/vrtsim/vrtsim.c | 186 ++++++++++++++++++------------------------ 1 file changed, 78 insertions(+), 108 deletions(-) diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index 97c820a859..9e678a5305 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -62,15 +63,20 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; #define TIME_SCALE_HLP \ "sample time scale. 1.0 means realtime. Values > 1 mean faster than realtime. Values < 1 mean slower than realtime\n" #define TAPS_SOCKET_HLP "Socket to connect to the channel emulation server\n" +#define CLIENT_NUM_RX_HLP "Number of RX antennas of the client, specified on the server\n" +#define CONNECTION_DESCRIPTOR_HLP "Path to the file written by the server that the client can use to connect." +#define DEFAULT_CHANNEL_NAME "vrtsim_channel" +#define DEFAULT_DESCRIPTOR "/tmp/vrtsim_connection" // clang-format off #define VRTSIM_PARAMS_DESC \ { \ - {"channel_name", "shared memory channel name\n", 0, .strptr = &channel_name, .defstrval = "vrtsim_channel", TYPE_STRING, 0}, \ - {"role", "either client or server\n", 0, .strptr = &role, .defstrval = ROLE_CLIENT_STRING, TYPE_STRING, 0}, \ - {"timescale", TIME_SCALE_HLP, 0, .dblptr = &vrtsim_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \ - {"chanmod", "Enable channel modelling", 0, .iptr = &vrtsim_state->chanmod, .defintval = 0, TYPE_INT, 0}, \ - {"taps-socket", TAPS_SOCKET_HLP, 0, .strptr = &vrtsim_state->taps_socket, .defstrval = NULL, TYPE_STRING, 0}, \ + {"connection_descriptor", CONNECTION_DESCRIPTOR_HLP, 0, .strptr = &vrtsim_state->connection_descriptor, .defstrval = DEFAULT_DESCRIPTOR, TYPE_STRING, 0}, \ + {"role", "either client or server\n", 0, .strptr = &role, .defstrval = ROLE_CLIENT_STRING, TYPE_STRING, 0}, \ + {"timescale", TIME_SCALE_HLP, 0, .dblptr = &vrtsim_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \ + {"chanmod", "Enable channel modelling", 0, .iptr = &vrtsim_state->chanmod, .defintval = 0, TYPE_INT, 0}, \ + {"taps-socket", TAPS_SOCKET_HLP, 0, .strptr = &vrtsim_state->taps_socket, .defstrval = NULL, TYPE_STRING, 0}, \ + {"client-num-rx-antennas", CLIENT_NUM_RX_HLP, 0, .iptr = &vrtsim_state->client_num_rx_antennas, .defintval = 1, TYPE_INT, 0}, \ }; // clang-format on @@ -96,7 +102,7 @@ typedef struct tx_timing_s { typedef struct { int role; - char channel_name[40]; + char *connection_descriptor; ShmTDIQChannel *channel; uint64_t last_received_sample; pthread_t timing_thread; @@ -116,6 +122,7 @@ typedef struct { channel_desc_t *channel_desc; Actor_t *channel_modelling_actors; char *taps_socket; + int client_num_rx_antennas; } vrtsim_state_t; // Sample history for channel impulse response @@ -174,12 +181,10 @@ static void load_channel_model(vrtsim_state_t *vrtsim_state) static void vrtsim_readconfig(vrtsim_state_t *vrtsim_state) { - char *channel_name = NULL; char *role = NULL; paramdef_t vrtsim_params[] = VRTSIM_PARAMS_DESC; int ret = config_get(config_get_if(), vrtsim_params, sizeofArray(vrtsim_params), VRTSIM_SECTION); AssertFatal(ret >= 0, "configuration couldn't be performed\n"); - strncpy(vrtsim_state->channel_name, channel_name, sizeof(vrtsim_state->channel_name) - 1); if (strncmp(role, ROLE_CLIENT_STRING, strlen(ROLE_CLIENT_STRING)) == 0) { vrtsim_state->role = ROLE_CLIENT; } else if (strncmp(role, ROLE_SERVER_STRING, strlen(ROLE_SERVER_STRING)) == 0) { @@ -230,112 +235,80 @@ static void *vrtsim_timing_job(void *arg) return 0; } +typedef struct client_info_s { + int server_num_rx_antennas; + int client_num_rx_antennas; +} client_info_t; + /** - * @brief Exchanges peer information between the server and the client. + * @brief Publishes the client information information to a file for the client to read. * - * This function sets up a Unix domain socket server, waits for a client to connect, - * and exchanges peer information (number of RX antennas) with the client. + * The server writes its client_info (number of RX antennas) to a file, which the client reads. + * The server does not wait for the client to write back; the client can connect at any point. * - * @param peer_info The peer information to send to the client. - * @return The peer information received from the client. - * - * @note This function will block until a client connects and the information is exchanged. + * @param client_info The client information to publish. + * @return The peer information (same as input, server is authoritative). */ -static peer_info_t server_exchange_peer_info(peer_info_t peer_info) +static void server_publish_client_info(client_info_t client_info, char *descriptor_file) { - int server_fd, client_fd; - - // Create a Unix domain socket - server_fd = socket(AF_UNIX, SOCK_STREAM, 0); - AssertFatal(server_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Bind the socket to a file path - struct sockaddr_un addr = {.sun_family = AF_UNIX}; - strncpy(addr.sun_path, "/tmp/vrtsim_socket", sizeof(addr.sun_path) - 1); - unlink(addr.sun_path); // Ensure the path does not already exist - int ret = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)); - AssertFatal(ret == 0, "bind() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Listen for incoming connections - ret = listen(server_fd, 1); - AssertFatal(ret == 0, "listen() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Accept a connection from a client - socklen_t addr_len = sizeof(addr); - client_fd = accept(server_fd, (struct sockaddr *)&addr, &addr_len); - AssertFatal(client_fd >= 0, "accept() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Send the antenna info to the server - ssize_t bytes_sent = send(client_fd, &peer_info, sizeof(peer_info), 0); - AssertFatal(bytes_sent == sizeof(peer_info), "send() failed: errno: %d, %s\n", errno, strerror(errno)); - - ssize_t bytes_received = recv(client_fd, &peer_info, sizeof(peer_info), 0); - AssertFatal(bytes_received == sizeof(peer_info), "recv() failed: errno: %d, %s\n", errno, strerror(errno)); - - LOG_I(HW, "Received client info: RX %d\n", peer_info.num_rx_antennas); - - // Close the client and server sockets - close(client_fd); - close(server_fd); - return peer_info; + FILE *fp = fopen(descriptor_file, "wb"); + AssertFatal(fp != NULL, "Failed to open client info file for writing: %s\n", strerror(errno)); + size_t written = fwrite(&client_info, sizeof(client_info), 1, fp); + AssertFatal(written == 1, "Failed to write client info to file\n"); + fclose(fp); } -/** - * @brief Exchanges peer information between the client and the server. - * - * This function connects to a Unix domain socket server, exchanges peer information - * (number of RX antennas) with the server, and returns the peer information received - * from the server. - * - * @param peer_info The peer information to send to the server. - * @return The peer information received from the server. - * - * @note This function will block until the information is exchanged. - */ -static peer_info_t client_exchange_peer_info(peer_info_t peer_info) +static client_info_t client_read_info(char *descriptor_file) { - int client_fd; - - // Create a Unix domain socket - client_fd = socket(AF_UNIX, SOCK_STREAM, 0); - AssertFatal(client_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Connect to the server - struct sockaddr_un addr = {.sun_family = AF_UNIX}; - strncpy(addr.sun_path, "/tmp/vrtsim_socket", sizeof(addr.sun_path) - 1); - int ret = -1; + client_info_t client_info; int tries = 0; - while (ret != 0 && tries < 10) { - ret = connect(client_fd, (struct sockaddr *)&addr, sizeof(addr)); - tries++; + while (tries < 10) { + FILE *fp = fopen(descriptor_file, "rb"); + if (fp) { + size_t read = fread(&client_info, sizeof(client_info), 1, fp); + fclose(fp); + if (read == 1) { + return client_info; + } + } sleep(1); + tries++; } - AssertFatal(ret == 0, "connect() failed: errno: %d, %s\n", errno, strerror(errno)); - - peer_info_t peer_info_received; - ssize_t bytes_received = recv(client_fd, &peer_info_received, sizeof(peer_info_received), 0); - AssertFatal(bytes_received == sizeof(peer_info_received), "recv() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Send the antenna info to the server - ssize_t bytes_sent = send(client_fd, &peer_info, sizeof(peer_info), 0); - AssertFatal(bytes_sent == sizeof(peer_info), "send() failed: errno: %d, %s\n", errno, strerror(errno)); - - LOG_I(HW, "Received peer info: RX %d\n", peer_info_received.num_rx_antennas); - - // Close the client socket - close(client_fd); - return peer_info_received; + AssertFatal(0, "Timeout waiting for client info\n"); + return client_info; } static int vrtsim_connect(openair0_device *device) { vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; - // Exchange peer info - peer_info_t peer_info = {.num_rx_antennas = device->openair0_cfg[0].rx_num_channels}; + + // Setup a shared memory channel if (vrtsim_state->role == ROLE_SERVER) { - vrtsim_state->peer_info = server_exchange_peer_info(peer_info); + vrtsim_state->peer_info.num_rx_antennas = vrtsim_state->client_num_rx_antennas; + vrtsim_state->channel = shm_td_iq_channel_create(DEFAULT_CHANNEL_NAME, + vrtsim_state->peer_info.num_rx_antennas, + device->openair0_cfg[0].rx_num_channels); + // Exchange peer info + client_info_t client_info = { + .server_num_rx_antennas = device->openair0_cfg[0].rx_num_channels, + .client_num_rx_antennas = vrtsim_state->client_num_rx_antennas, + }; + server_publish_client_info(client_info, vrtsim_state->connection_descriptor); + + vrtsim_state->run_timing_thread = true; + int ret = pthread_create(&vrtsim_state->timing_thread, NULL, vrtsim_timing_job, vrtsim_state); + AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno)); } else { - vrtsim_state->peer_info = client_exchange_peer_info(peer_info); + client_info_t client_info = client_read_info(vrtsim_state->connection_descriptor); + AssertFatal(client_info.server_num_rx_antennas > 0, "Server did not publish valid client info, aborting client connection\n"); + AssertFatal( + client_info.client_num_rx_antennas == device->openair0_cfg[0].rx_num_channels, + "Server expects different number of RX antennas. %d != %d. Use server command line option --client-num-rx-antennas\n", + client_info.client_num_rx_antennas, + device->openair0_cfg[0].rx_num_channels); + vrtsim_state->channel = shm_td_iq_channel_connect(DEFAULT_CHANNEL_NAME, 10); + vrtsim_state->peer_info.num_rx_antennas = client_info.server_num_rx_antennas; + vrtsim_state->last_received_sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel); } // Handle channel modelling after number of RX antennas are known @@ -363,18 +336,6 @@ static int vrtsim_connect(openair0_device *device) vrtsim_state->tx_timing[i].tx_histogram.range = 3000.0; } - // Setup a shared memory channel - if (vrtsim_state->role == ROLE_SERVER) { - vrtsim_state->channel = shm_td_iq_channel_create(vrtsim_state->channel_name, - vrtsim_state->peer_info.num_rx_antennas, - device->openair0_cfg[0].rx_num_channels); - vrtsim_state->run_timing_thread = true; - int ret = pthread_create(&vrtsim_state->timing_thread, NULL, vrtsim_timing_job, vrtsim_state); - AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno)); - } else { - vrtsim_state->channel = shm_td_iq_channel_connect(vrtsim_state->channel_name, 10); - } - return 0; } @@ -640,6 +601,15 @@ static void vrtsim_end(openair0_device *device) LOG_I(HW, "VRTSIM: Average TX budget %.3lf uS (more is better)\n", tx_timing->average_tx_budget); histogram_print(&tx_timing->tx_histogram); free(vrtsim_state->tx_timing); + + if (vrtsim_state->role == ROLE_SERVER) { + int ret = remove(vrtsim_state->connection_descriptor); + if (ret != 0) { + LOG_E(HW, "Failed to remove connection descriptor file %s: %s\n", vrtsim_state->connection_descriptor, strerror(errno)); + } else { + LOG_A(HW, "Removed connection descriptor file %s\n", vrtsim_state->connection_descriptor); + } + } } static int vrtsim_stub(openair0_device *device) From 74fc56813f8157ca7051768f1121004d1db90fcf Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 8 Aug 2025 17:55:12 +0200 Subject: [PATCH 08/10] Update vrtsim documentation --- radio/vrtsim/README.md | 80 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/radio/vrtsim/README.md b/radio/vrtsim/README.md index 71455175c3..2cd90e7886 100644 --- a/radio/vrtsim/README.md +++ b/radio/vrtsim/README.md @@ -4,12 +4,24 @@ This implements a shared memory realtime and near-realtime radio interface. This is performed using `shm_td_iq_channel` which handles the shared memory interface. +# Architecture + +The `vrtsim` architecture follows a server-client model: + +- **Server (gNB)**: On startup, the server writes a file, default location is + `/tmp/vrtsim_connection`. This file contains information required by the client + to establish communication. +- **Client (UE)**: The client reads this file from, verifies its contents, and uses + the provided information to open and connect to the shared memory channel. +- **Shared Memory Channel**: Both server and client use the `shm_td_iq_channel` to + exchange IQ samples and control data in real time or near-real time. + +## Channel modelling: + + # Limitations - Only 1gNB to 1nrUE: gNB as a server, UE as a client. - - Server only accepts connections during device bringup. This means that - restarting the client will not make it reconnect, the server has to be - restarted as well. # Usage @@ -21,5 +33,63 @@ is the default and means realtime. Channel modelling can be enabled by adding `vrtsim.chanmod 1` to the command line and should work the same as channel modelling in rfsimulator, -see rfsimulator [documentation](../rfsimulator/README.md), provided that your -CPU is fast enough. +see rfsimulator [documentation](../rfsimulator/README.md) with a slight difference: +channel modelling is done on transmission instead of reception. To refect this, +the model names are slightly different: the server expects a model named +`server_tx_channel_model` while the client expects a model named `client_tx_channel_model` + +Additionally, `taps_client` is available in vrtsim. This allows to connect to +channel emulation server and receive external taps (or channel impulse responses). +To use external taps, use `--taps-socket` and provide a nanomsg PUB socket address +that will publish the taps. + +# Debugging + +## Realtime issues + +Realtime issues can prevent the UE from connecting to the gNB, especially with +channel modelling enabled. Consider adjusting `timescale` parameter until +the UE can connect reliably. + +On exit, vrtsim will output a histogram of the measured transmission budget, which +is defined as the number of microseconds before the transmitted samples could be +read by peer. + +Example: + +``` +[HW] VRTSIM: Average TX budget 939.210 uS (more is better) +[HW] VRTSIM: TX budget histogram: 216 samples +[HW] Bin 0 [0.0 - 100.0uS]: 0 +[HW] Bin 1 [100.0 - 200.0uS]: 0 +[HW] Bin 2 [200.0 - 300.0uS]: 0 +[HW] Bin 3 [300.0 - 400.0uS]: 0 +[HW] Bin 4 [400.0 - 500.0uS]: 0 +[HW] Bin 5 [500.0 - 600.0uS]: 0 +[HW] Bin 6 [600.0 - 700.0uS]: 0 +[HW] Bin 7 [700.0 - 800.0uS]: 0 +[HW] Bin 8 [800.0 - 900.0uS]: 0 +[HW] Bin 9 [900.0 - 1000.0uS]: 117 +[HW] Bin 10 [1000.0 - 1100.0uS]: 0 +[HW] Bin 11 [1100.0 - 1200.0uS]: 0 +[HW] Bin 12 [1200.0 - 1300.0uS]: 0 +[HW] Bin 13 [1300.0 - 1400.0uS]: 0 +[HW] Bin 14 [1400.0 - 1500.0uS]: 0 +[HW] Bin 15 [1500.0 - 1600.0uS]: 0 +[HW] Bin 16 [1600.0 - 1700.0uS]: 0 +[HW] Bin 17 [1700.0 - 1800.0uS]: 0 +[HW] Bin 18 [1800.0 - 1900.0uS]: 0 +[HW] Bin 19 [1900.0 - 2000.0uS]: 0 +[HW] Bin 20 [2000.0 - 2100.0uS]: 0 +[HW] Bin 21 [2100.0 - 2200.0uS]: 0 +[HW] Bin 22 [2200.0 - 2300.0uS]: 0 +[HW] Bin 23 [2300.0 - 2400.0uS]: 0 +[HW] Bin 24 [2400.0 - 2500.0uS]: 0 +[HW] Bin 25 [2500.0 - 2600.0uS]: 0 +[HW] Bin 26 [2600.0 - 2700.0uS]: 0 +[HW] Bin 27 [2700.0 - 2800.0uS]: 0 +[HW] Bin 28 [2800.0 - 2900.0uS]: 0 +[HW] Bin 29 [2900.0 - 3000.0uS]: 0 +``` + +Samples in bin 0 indicate realtime issues. From 171b3f206f415f28fda86d6fa2934d384b77de0b Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Mon, 18 Aug 2025 09:43:34 +0200 Subject: [PATCH 09/10] VRTsim: Add a timeout in taps client Add a timeout in nanomsg receive code to allow the client thread to exit cleanly. --- radio/vrtsim/taps_client.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/radio/vrtsim/taps_client.cpp b/radio/vrtsim/taps_client.cpp index 6ebfc450fd..c314c3981c 100644 --- a/radio/vrtsim/taps_client.cpp +++ b/radio/vrtsim/taps_client.cpp @@ -108,8 +108,13 @@ void *client_thread_func(void *args) while (should_run) { int next_buffer = (taps_storage.current_buffer + 1) % NUM_TAPS_BUFFERS; taps_buffer_t *taps_buffer = &taps_storage.taps_buffers[next_buffer]; - int ret = nn_recv(client_thread_args->sock, taps_buffer->taps_msg, MAX_TAPS_MSG_SIZE, 0); + int ret = nn_recv(client_thread_args->sock, taps_buffer->taps_msg, MAX_TAPS_MSG_SIZE, NN_DONTWAIT); if (ret < 0) { + if (errno == EAGAIN) { + // Timeout: no message available, sleep briefly and continue + usleep(100); + continue; + } LOG_E(HW, "nn_recv() failed: errno: %d, %s\n", errno, strerror(errno)); continue; } From 513173ae0d1bc6a1304c663ecd0407042a786919 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Mon, 18 Aug 2025 09:51:27 +0200 Subject: [PATCH 10/10] Rename VRTSIM cmake option to OAI_VRTSIM --- radio/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/radio/CMakeLists.txt b/radio/CMakeLists.txt index 654fb037f0..e68c84ec90 100644 --- a/radio/CMakeLists.txt +++ b/radio/CMakeLists.txt @@ -38,7 +38,7 @@ if(OAI_USRP) add_subdirectory(USRP) endif() -add_boolean_option(VRTSIM_RADIO ON "Activate OAI's shared memory radio driver" OFF) -if(VRTSIM_RADIO) +add_boolean_option(OAI_VRTSIM ON "Activate OAI's shared memory radio driver" OFF) +if(OAI_VRTSIM) add_subdirectory(vrtsim) endif()