From 1d1814f8b34a5271ceba37ef5c4c1120bc75d340 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 14 Jan 2025 14:27:05 +0100 Subject: [PATCH 1/8] Shared memory time domain bidirectional IQ channel This implements a server-client bidirectional time domain IQ channel via shared memory. The server creates the channel and should be aware of the client receiver configuration (num rx/tx antennas). Server shall be the time source and should call shm_iq_channel_produce_samples at regular intervals --- common/utils/CMakeLists.txt | 1 + common/utils/shm_iq_channel/CMakeLists.txt | 6 + .../utils/shm_iq_channel/shm_td_iq_channel.c | 286 ++++++++++++++++++ .../utils/shm_iq_channel/shm_td_iq_channel.h | 148 +++++++++ .../utils/shm_iq_channel/tests/CMakeLists.txt | 5 + .../tests/test_shm_td_iq_channel.c | 169 +++++++++++ 6 files changed, 615 insertions(+) create mode 100644 common/utils/shm_iq_channel/CMakeLists.txt create mode 100644 common/utils/shm_iq_channel/shm_td_iq_channel.c create mode 100644 common/utils/shm_iq_channel/shm_td_iq_channel.h create mode 100644 common/utils/shm_iq_channel/tests/CMakeLists.txt create mode 100644 common/utils/shm_iq_channel/tests/test_shm_td_iq_channel.c diff --git a/common/utils/CMakeLists.txt b/common/utils/CMakeLists.txt index ccb15bfd47..b311d0542f 100644 --- a/common/utils/CMakeLists.txt +++ b/common/utils/CMakeLists.txt @@ -21,3 +21,4 @@ if (ENABLE_TESTS) endif() add_subdirectory(barrier) add_subdirectory(actor) +add_subdirectory(shm_iq_channel) diff --git a/common/utils/shm_iq_channel/CMakeLists.txt b/common/utils/shm_iq_channel/CMakeLists.txt new file mode 100644 index 0000000000..57c7d343ef --- /dev/null +++ b/common/utils/shm_iq_channel/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(shm_td_iq_channel shm_td_iq_channel.c) +target_include_directories(shm_td_iq_channel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +if (ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/common/utils/shm_iq_channel/shm_td_iq_channel.c b/common/utils/shm_iq_channel/shm_td_iq_channel.c new file mode 100644 index 0000000000..89e09ddb1f --- /dev/null +++ b/common/utils/shm_iq_channel/shm_td_iq_channel.c @@ -0,0 +1,286 @@ +/* + * 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 "shm_td_iq_channel.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "assertions.h" +#include "utils.h" +#include "common/utils/threadPool/pthread_utils.h" + +#define CIRCULAR_BUFFER_SIZE (30720 * 14 * 20) + +typedef struct { + int magic; + int num_antennas_tx; + int num_antennas_rx; + uint64_t timestamp; + bool is_connected; + pthread_mutex_t mutex; + pthread_cond_t cond; +} ShmTDIQChannelData; + +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; +} ShmTDIQChannel; + +ShmTDIQChannel *shm_td_iq_channel_create(const char *name, int num_tx_ant, int num_rx_ant) +{ + // Create shared memory segment + int fd = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + AssertFatal(fd != -1, "shm_open failed: %s\n", strerror(errno)); + size_t tx_buffer_size = CIRCULAR_BUFFER_SIZE * sizeof(sample_t) * num_tx_ant; + size_t rx_buffer_size = CIRCULAR_BUFFER_SIZE * sizeof(sample_t) * num_rx_ant; + size_t total_size = sizeof(ShmTDIQChannelData) + tx_buffer_size + rx_buffer_size; + + // Set the size of the shared memory segment + int res = ftruncate(fd, total_size); + AssertFatal(res != -1, "ftruncate failed: %s\n", strerror(errno)); + + // Map shared memory segment to address space + ShmTDIQChannelData *shm_ptr = mmap(NULL, total_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + AssertFatal(shm_ptr != MAP_FAILED, "mmap failed: %s\n", strerror(errno)); + + // Initialize shared memory + 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); + AssertFatal(ret == 0, "pthread_mutexattr_init() failed: errno %d, %s\n", errno, strerror(errno)); + + ret = pthread_condattr_init(&cond_attr); + AssertFatal(ret == 0, "pthread_condattr_init() failed: errno %d, %s\n", errno, strerror(errno)); + + ret = pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED); + AssertFatal(ret == 0, "pthread_mutexattr_setpshared() failed: errno %d, %s\n", errno, strerror(errno)); + + ret = pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED); + AssertFatal(ret == 0, "pthread_condattr_setpshared() failed: errno %d, %s\n", errno, strerror(errno)); + + ret = pthread_mutex_init(&shm_ptr->mutex, &mutex_attr); + AssertFatal(ret == 0, "pthread_mutex_init() failed: errno %d, %s\n", errno, strerror(errno)); + + ret = pthread_cond_init(&shm_ptr->cond, &cond_attr); + AssertFatal(ret == 0, "pthread_cond_init() failed: errno %d, %s\n", errno, strerror(errno)); + + shm_ptr->magic = SHM_MAGIC_NUMBER; + close(fd); + return channel; +} + +ShmTDIQChannel *shm_td_iq_channel_connect(const char *name, int timeout_in_seconds) +{ + // Create shared memory segment + int fd = -1; + while (timeout_in_seconds > 0 && fd == -1) { + fd = shm_open(name, O_RDWR, S_IRUSR | S_IWUSR); + timeout_in_seconds--; + printf("Waiting for server to create shared memory segment\n"); + sleep(1); + } + AssertFatal(fd != -1, "shm_open() failed: errno %d, %s", errno, strerror(errno)); + + struct stat buf; + fstat(fd, &buf); + size_t total_size = buf.st_size; + + // Map shared memory segment to address space + ShmTDIQChannelData *shm_ptr = mmap(NULL, total_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (shm_ptr == MAP_FAILED) { + perror("mmap"); + exit(1); + } + + ShmTDIQChannel *channel = calloc_or_fail(1, sizeof(ShmTDIQChannel)); + channel->data = shm_ptr; + channel->tx_iq_data = (sample_t *)(shm_ptr + 1); + 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; +} + +IQChannelErrorType shm_td_iq_channel_tx(ShmTDIQChannel *channel, + uint64_t timestamp, + uint64_t num_samples, + int antenna, + 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) { + return CHANNEL_ERROR_TOO_LATE; + } + // timestamp is too far in the future + if (timestamp - current_time + num_samples >= CIRCULAR_BUFFER_SIZE) { + return CHANNEL_ERROR_TOO_EARLY; + } + + sample_t *base_ptr; + if (channel->type == IQ_CHANNEL_TYPE_CLIENT) { + base_ptr = channel->rx_iq_data + antenna * CIRCULAR_BUFFER_SIZE; + } else { + base_ptr = channel->tx_iq_data + antenna * CIRCULAR_BUFFER_SIZE; + } + + uint64_t first_sample = timestamp % CIRCULAR_BUFFER_SIZE; + uint64_t last_sample = first_sample + num_samples - 1; + if (last_sample >= CIRCULAR_BUFFER_SIZE) { + size_t num_samples_first_copy = CIRCULAR_BUFFER_SIZE - first_sample; + memcpy(base_ptr + first_sample, tx_iq_data, num_samples_first_copy * sizeof(sample_t)); + memcpy(base_ptr, tx_iq_data + num_samples_first_copy, (num_samples - num_samples_first_copy) * sizeof(sample_t)); + } else { + memcpy(base_ptr + first_sample, tx_iq_data, num_samples * sizeof(sample_t)); + } + return CHANNEL_NO_ERROR; +} + +IQChannelErrorType shm_td_iq_channel_rx(ShmTDIQChannel *channel, + uint64_t timestamp, + uint64_t num_samples, + int antenna, + 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) { + return CHANNEL_ERROR_TOO_EARLY; + } + // timestamp is too far in the past + if (current_time - timestamp >= CIRCULAR_BUFFER_SIZE) { + return CHANNEL_ERROR_TOO_LATE; + } + + sample_t *base_ptr; + if (channel->type == IQ_CHANNEL_TYPE_CLIENT) { + base_ptr = channel->tx_iq_data + antenna * CIRCULAR_BUFFER_SIZE; + } else { + base_ptr = channel->rx_iq_data + antenna * CIRCULAR_BUFFER_SIZE; + } + + uint64_t first_sample = timestamp % CIRCULAR_BUFFER_SIZE; + uint64_t last_sample = first_sample + num_samples - 1; + if (last_sample >= CIRCULAR_BUFFER_SIZE) { + size_t num_samples_first_copy = CIRCULAR_BUFFER_SIZE - first_sample; + memcpy(tx_iq_data, base_ptr + first_sample, num_samples_first_copy * sizeof(sample_t)); + memcpy(tx_iq_data + num_samples_first_copy, base_ptr, (num_samples - num_samples_first_copy) * sizeof(sample_t)); + } else { + memcpy(tx_iq_data, base_ptr + first_sample, num_samples * sizeof(sample_t)); + } + return CHANNEL_NO_ERROR; +} + +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; + } + mutexlock(data->mutex); + data->timestamp += num_samples; + condbroadcast(data->cond); + mutexunlock(data->mutex); +} + +void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp) +{ + ShmTDIQChannelData *data = channel->data; + if (data->is_connected == false) { + abort(); + return; + } + + size_t current_timestamp = data->timestamp; + if (current_timestamp >= timestamp) { + return; + } + mutexlock(data->mutex); + while (current_timestamp < timestamp) { + condwait(data->cond, data->mutex); + current_timestamp = data->timestamp; + } + mutexunlock(data->mutex); + return; +} + +uint64_t shm_td_iq_channel_get_current_sample(const ShmTDIQChannel *channel) +{ + ShmTDIQChannelData *data = channel->data; + return data->timestamp; +} + +bool shm_td_iq_channel_is_connected(const ShmTDIQChannel *channel) +{ + return channel->data->is_connected; +} + +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) { + shm_unlink(channel->name); + } + 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 new file mode 100644 index 0000000000..7c5a730914 --- /dev/null +++ b/common/utils/shm_iq_channel/shm_td_iq_channel.h @@ -0,0 +1,148 @@ +/* + * 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 SHM_IQ_CHANNEL_H +#define SHM_IQ_CHANNEL_H + +#include "../threadPool/pthread_utils.h" +#include +#include +#include + +#define SHM_MAGIC_NUMBER 0x12345678 + +/** + * ShmTdIqChannel is a shared memory bidirectional time domain IQ channel with a single clock + * source. The server (clock source) shall create the channel while the client should connect to + * it. + * + * To write samples, send them via a pointer passed to shm_td_iq_channel_tx. + * To read samples, receive them via a pointer passed to shm_td_iq_channel_rx. You can either wait until + * the sample are available using shm_td_iq_channel_wait or poll using shm_td_iq_channel_rx until the return + * value is not CHANNEL_ERROR_TOO_EARLY. + * To indicate that samples are ready to be read by the client, call shm_td_iq_channel_produce_samples + * (server only). + * + * The timestamps used in the API are in absolute samples from the time the client/server connection started. + */ + +typedef enum IQChannelType { IQ_CHANNEL_TYPE_SERVER, IQ_CHANNEL_TYPE_CLIENT } IQChannelType; +typedef uint32_t sample_t; +typedef enum IQChannelErrorType { + CHANNEL_NO_ERROR, + CHANNEL_ERROR_TOO_LATE, + CHANNEL_ERROR_TOO_EARLY, + CHANNEL_ERROR_NOT_CONNECTED +} IQChannelErrorType; + +typedef struct ShmTDIQChannel_s ShmTDIQChannel; + +/** + * @brief Creates a shared memory IQ channel. + * + * @param name The name of the shared memory segment. + * @param num_tx_ant The number of TX antennas. + * @param num_rx_ant The number of RX antennas. + * @return A pointer to the created ShmTDIQChannel structure. + */ +ShmTDIQChannel *shm_td_iq_channel_create(const char *name, int num_tx_ant, int num_rx_ant); + +/** + * @brief Connects to an existing shared memory IQ channel. + * + * @param name The name of the shared memory segment. + * @param timeout_in_seconds The timeout in seconds for the connection attempt. + * @return A pointer to the connected ShmTDIQChannel structure. + */ +ShmTDIQChannel *shm_td_iq_channel_connect(const char *name, int timeout_in_seconds); + +/** + * @brief Transmit data in the channel + * + * @param channel The ShmTDIQChannel structure. + * @param timestamp The timestamp for which to get the TX IQ data slot. + * @param num_samples The number of samples to write. + * @param antenna The antenna index. + * @param tx_iq_data The TX IQ data to write. + * + * @return CHANNEL_NO_ERROR if successful, error type otherwise + */ +IQChannelErrorType shm_td_iq_channel_tx(ShmTDIQChannel *channel, + uint64_t timestamp, + uint64_t num_samples, + int antenna, + const sample_t *tx_iq_data); + +/** + * @brief Receive iq data from the channel + * + * @param channel The ShmTDIQChannel structure. + * @param timestamp The timestamp for which to get the RX IQ data slot. + * @param num_samples The number of samples to read. + * @param antenna The antenna index. + * @param tx_iq_data pointer to the RX IQ data slot. + * @return CHANNEL_NO_ERROR if successful, error type otherwise + */ +IQChannelErrorType shm_td_iq_channel_rx(ShmTDIQChannel *channel, + uint64_t timestamp, + uint64_t num_samples, + int antenna, + sample_t *tx_iq_data); + +/** + * @brief Advances the time in the channel by specified number of samples + * + * @param channel The ShmTDIQChannel structure. + * @param num_symbols The number of samples to produce. + */ +void shm_td_iq_channel_produce_samples(ShmTDIQChannel *channel, uint64_t num_samples); + +/** + * @brief Wait until sample at the specified timestamp is available + * + * @param channel The ShmTDIQChannel structure. + */ +void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp); + +/** + * @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 Destroys the shared memory IQ channel. + * + * @param channel The ShmTDIQChannel structure. + */ +void shm_td_iq_channel_destroy(ShmTDIQChannel *channel); + +/** + * @brief Returns current sample + * + * @param channel The ShmTDIQChannel structure. + * + * @return Current time as sample count since beginning of transmission + */ +uint64_t shm_td_iq_channel_get_current_sample(const ShmTDIQChannel *channel); + +#endif diff --git a/common/utils/shm_iq_channel/tests/CMakeLists.txt b/common/utils/shm_iq_channel/tests/CMakeLists.txt new file mode 100644 index 0000000000..f80f9cffbc --- /dev/null +++ b/common/utils/shm_iq_channel/tests/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(test_shm_td_iq_channel test_shm_td_iq_channel.c) +target_link_libraries(test_shm_td_iq_channel shm_td_iq_channel minimal_lib pthread) +add_dependencies(tests test_shm_td_iq_channel) +# Commented out due to issues with ASAN in the unit test docker container +#add_test(test_shm_td_iq_channel ./test_shm_td_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 new file mode 100644 index 0000000000..f5170b8144 --- /dev/null +++ b/common/utils/shm_iq_channel/tests/test_shm_td_iq_channel.c @@ -0,0 +1,169 @@ +/* + * 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 +#include "assertions.h" + +#define SHM_CHANNEL_NAME "shm_iq_channel_test_file" +#include "shm_td_iq_channel.h" + +void server(void); +int client(void); + +enum { MODE_SERVER, MODE_CLIENT, MODE_FORK }; +int main(int argc, char *argv[]) +{ + int mode = MODE_FORK; + if (argc == 2) { + if (strcmp(argv[1], "server") == 0) { + mode = MODE_SERVER; + } else if (strcmp(argv[1], "client") == 0) { + mode = MODE_CLIENT; + } + } else if (argc > 2) { + printf("Usage: %s [server|client]\n", argv[0]); + return 1; + } + switch (mode) { + case MODE_SERVER: + server(); + break; + case MODE_CLIENT: + return client(); + break; + case MODE_FORK: { + char file[256]; + snprintf(file, sizeof(file), "/dev/shm/%s", SHM_CHANNEL_NAME); + remove(file); + int pid = fork(); + if (pid == 0) { + server(); + } else { + return client(); + } + } break; + } + + return 0; +} + +const int num_updates = 50; +const int num_samples_per_update = 30720; +void *produce_symbols(void *arg) +{ + ShmTDIQChannel *channel = (ShmTDIQChannel *)arg; + for (int i = 0; i < num_updates; i++) { + shm_td_iq_channel_produce_samples(channel, num_samples_per_update); + usleep(20000); + } + return 0; +} + +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); + AssertFatal(ret == 0, "pthread_create() failed: errno %d, %s\n", errno, strerror(errno)); + + 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); + uint64_t target_timestamp = timestamp + num_samples_per_update; + timestamp += num_samples_per_update; + uint32_t iq_data[num_samples_per_update]; + for (int i = 0; i < num_samples_per_update; i++) { + iq_data[i] = iq_contents; + } + int result = shm_td_iq_channel_tx(channel, target_timestamp, num_samples_per_update, 0, iq_data); + AssertFatal(result == CHANNEL_NO_ERROR, "Failed to write data\n"); + iq_contents++; + } + + printf("Finished writing data\n"); + + ret = pthread_join(producer_thread, NULL); + AssertFatal(ret == 0, "pthread_join() failed: errno %d, %s\n", errno, strerror(errno)); + shm_td_iq_channel_destroy(channel); +} + +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; + + while (timestamp < num_samples_per_update * num_updates) { + shm_td_iq_channel_wait(channel, timestamp + num_samples_per_update); + // Server starts producing from second slot + if (timestamp > num_samples_per_update) { + uint64_t target_timestamp = timestamp - num_samples_per_update; + uint32_t iq_data[num_samples_per_update]; + int result = shm_td_iq_channel_rx(channel, target_timestamp, num_samples_per_update, 0, iq_data); + AssertFatal(result == CHANNEL_NO_ERROR, "Failed to read data\n"); + int num_errors = 0; + for (int i = 0; i < num_samples_per_update; i++) { + if (iq_data[i] != iq_contents) { + num_errors++; + } + } + if (num_errors) { + printf("Found %d errors, value = %d, reference = %d\n", num_errors, iq_data[0], iq_contents); + total_errors += num_errors; + } + iq_contents++; + } + timestamp += num_samples_per_update; + } + + printf("Finished reading data\n"); + shm_td_iq_channel_destroy(channel); + return total_errors; +} From 1a69a4efe1d00bde29eb30018d5e03d93a47bbf8 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 11 Feb 2025 12:14:34 +0100 Subject: [PATCH 2/8] Shared memory realtime radio simulation Realtime and near-realtime RFSimulator alternative without channel modelling. The purpose of this library is to allow realtime simulation of gNB and nrUE. The library is loaded using --device.name shm_radio. The server side (gNB) should also configure --shm_radio.role server and --shm_radio.timescale according to requirements. Limitations: - Only 1UE-to-1gNB connection supported Future work: - Channel modelling - Telnet API - Histogram of the TX time budget --- doc/README.md | 1 + radio/CMakeLists.txt | 5 + radio/shm_radio/CMakeLists.txt | 4 + radio/shm_radio/README.md | 19 +++ radio/shm_radio/shm_radio.c | 240 +++++++++++++++++++++++++++++++++ 5 files changed, 269 insertions(+) create mode 100644 radio/shm_radio/CMakeLists.txt create mode 100644 radio/shm_radio/README.md create mode 100644 radio/shm_radio/shm_radio.c diff --git a/doc/README.md b/doc/README.md index 4786458a66..30e0116abf 100644 --- a/doc/README.md +++ b/doc/README.md @@ -118,6 +118,7 @@ Some directories under `radio` contain READMEs: - [BladeRF](../radio/BLADERF/README) - [IQPlayer](../radio/iqplayer/DOC/iqrecordplayer_usage.md), and [general documentation](./iqrecordplayer_usage.md) - [fhi_72](../radio/fhi_72/README.md) +- [shm_radio](../radio/shm_radio/README.md) The other SDRs (AW2S, LimeSDR, ...) have no READMEs. diff --git a/radio/CMakeLists.txt b/radio/CMakeLists.txt index fb9086e2eb..a2da0ac616 100644 --- a/radio/CMakeLists.txt +++ b/radio/CMakeLists.txt @@ -37,3 +37,8 @@ add_boolean_option(OAI_USRP OFF "Activate OAI's USRP driver" OFF) if(OAI_USRP) add_subdirectory(USRP) endif() + +add_boolean_option(OAI_SHM_RADIO ON "Activate OAI's shared memory radio driver" OFF) +if(OAI_SHM_RADIO) + add_subdirectory(shm_radio) +endif() diff --git a/radio/shm_radio/CMakeLists.txt b/radio/shm_radio/CMakeLists.txt new file mode 100644 index 0000000000..dcaf3b6e1d --- /dev/null +++ b/radio/shm_radio/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(shm_radio MODULE shm_radio.c) +set_target_properties(shm_radio PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +target_link_libraries(shm_radio PRIVATE shm_td_iq_channel) +add_dependencies(shm_radio generate_T) diff --git a/radio/shm_radio/README.md b/radio/shm_radio/README.md new file mode 100644 index 0000000000..6d66878110 --- /dev/null +++ b/radio/shm_radio/README.md @@ -0,0 +1,19 @@ +# Overview + +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. + +# Limitations + + - Only 1gNB to 1nrUE + - Only 1rx and 1tx antennas + - No channel modelling + +# Usage + +On the UE and gNB: use `device.name shm_radio` command line argument. + +Additionally on gNB use `shm_radio.role server` and optionally +`shm_radio.timescale ` to set the timescale. Timescale 1.0 +is the default and means realtime. diff --git a/radio/shm_radio/shm_radio.c b/radio/shm_radio/shm_radio.c new file mode 100644 index 0000000000..9962a996f7 --- /dev/null +++ b/radio/shm_radio/shm_radio.c @@ -0,0 +1,240 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "common_lib.h" +#include "shm_td_iq_channel.h" + +// Simulator role +typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; + +#define ROLE_CLIENT_STRING "client" +#define ROLE_SERVER_STRING "server" + +#define SHM_RADIO_SECTION "shm_radio" +#define TIME_SCALE_HLP \ + "sample time scale. 1.0 means realtime. Values > 1 mean faster than realtime. Values < 1 mean slower than realtime\n" + +// clang-format off +#define SHM_RADIO_PARAMS_DESC \ + { \ + {"channel_name", "shared memory channel name\n", 0, .strptr = &channel_name, .defstrval = "shm_radio_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 = &shm_radio_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \ + }; +// clang-format on + +typedef struct { + int role; + char channel_name[40]; + ShmTDIQChannel *channel; + uint64_t last_received_sample; + pthread_t timing_thread; + bool run_timing_thread; + double timescale; + double sample_rate; + uint64_t tx_samples_late; + uint64_t tx_early; + uint64_t tx_samples_total; + uint64_t rx_samples_late; + uint64_t rx_early; + uint64_t rx_samples_total; + double average_tx_budget; +} shm_radio_state_t; + +static void shm_radio_readconfig(shm_radio_state_t *shm_radio_state) +{ + char *channel_name = NULL; + char *role = NULL; + paramdef_t shm_radio_params[] = SHM_RADIO_PARAMS_DESC; + int ret = config_get(config_get_if(), shm_radio_params, sizeofArray(shm_radio_params), SHM_RADIO_SECTION); + AssertFatal(ret >= 0, "configuration couldn't be performed\n"); + strncpy(shm_radio_state->channel_name, channel_name, sizeof(shm_radio_state->channel_name) - 1); + if (strncmp(role, ROLE_CLIENT_STRING, strlen(ROLE_CLIENT_STRING)) == 0) { + shm_radio_state->role = ROLE_CLIENT; + } else if (strncmp(role, ROLE_SERVER_STRING, strlen(ROLE_SERVER_STRING)) == 0) { + shm_radio_state->role = ROLE_SERVER; + } else { + AssertFatal(false, "Invalid role configuration\n"); + } +} + +static void *shm_radio_timing_job(void *arg) +{ + shm_radio_state_t *shm_radio_state = arg; + struct timespec timestamp; + if (clock_gettime(CLOCK_REALTIME, ×tamp)) { + LOG_E(UTIL, "clock_gettime failed\n"); + exit(1); + } + double leftover_samples = 0; + while (shm_radio_state->run_timing_thread) { + struct timespec current_time; + if (clock_gettime(CLOCK_REALTIME, ¤t_time)) { + LOG_E(UTIL, "clock_gettime failed\n"); + exit(1); + } + uint64_t diff = (current_time.tv_sec - timestamp.tv_sec) * 1000000000 + (current_time.tv_nsec - timestamp.tv_nsec); + timestamp = current_time; + double samples_to_produce = shm_radio_state->sample_rate * shm_radio_state->timescale * diff / 1e9; + + // Attempt to correct compounding rounding error + leftover_samples += samples_to_produce - (uint64_t)samples_to_produce; + if (leftover_samples > 1.0f) { + samples_to_produce += 1; + leftover_samples -= 1; + } + shm_td_iq_channel_produce_samples(shm_radio_state->channel, samples_to_produce); + usleep(1); + } + return 0; +} + +static int shm_radio_connect(openair0_device *device) +{ + shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv; + if (shm_radio_state->role == ROLE_SERVER) { + shm_radio_state->channel = shm_td_iq_channel_create(shm_radio_state->channel_name, 1, 1); + shm_radio_state->run_timing_thread = true; + while (!shm_td_iq_channel_is_connected(shm_radio_state->channel)) { + LOG_I(HW, "Waiting for client\n"); + sleep(1); + } + int ret = pthread_create(&shm_radio_state->timing_thread, NULL, shm_radio_timing_job, shm_radio_state); + AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno)); + } else { + shm_radio_state->channel = shm_td_iq_channel_connect(shm_radio_state->channel_name, 10); + } + return 0; +} + +static int shm_radio_write(openair0_device *device, + openair0_timestamp timestamp, + void **samplesVoid, + int nsamps, + int nbAnt, + int flags) +{ + timestamp -= device->openair0_cfg->command_line_sample_advance; + shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv; + uint64_t sample = shm_td_iq_channel_get_current_sample(shm_radio_state->channel); + int64_t diff = timestamp - sample; + double budget = diff / (shm_radio_state->sample_rate / 1e6); + shm_radio_state->average_tx_budget = .05 * budget + .95 * shm_radio_state->average_tx_budget; + + int ret = shm_td_iq_channel_tx(shm_radio_state->channel, timestamp, nsamps, 0, samplesVoid[0]); + if (ret == CHANNEL_ERROR_TOO_LATE) { + shm_radio_state->tx_samples_late += nsamps; + } else if (ret == CHANNEL_ERROR_TOO_EARLY) { + shm_radio_state->tx_early += 1; + } + shm_radio_state->tx_samples_total += nsamps; + return nsamps; +} + +static int shm_radio_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) +{ + shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv; + shm_td_iq_channel_wait(shm_radio_state->channel, shm_radio_state->last_received_sample + nsamps); + int ret = shm_td_iq_channel_rx(shm_radio_state->channel, shm_radio_state->last_received_sample, nsamps, 0, samplesVoid[0]); + if (ret == CHANNEL_ERROR_TOO_LATE) { + shm_radio_state->rx_samples_late += nsamps; + } else if (ret == CHANNEL_ERROR_TOO_EARLY) { + shm_radio_state->rx_early += 1; + } + shm_radio_state->rx_samples_total += nsamps; + *ptimestamp = shm_radio_state->last_received_sample; + shm_radio_state->last_received_sample += nsamps; + return nsamps; +} + +static void shm_radio_end(openair0_device *device) +{ + shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv; + if (shm_radio_state->role == ROLE_SERVER) { + shm_radio_state->run_timing_thread = false; + int ret = pthread_join(shm_radio_state->timing_thread, NULL); + AssertFatal(ret == 0, "pthread_join() failed: errno: %d, %s\n", errno, strerror(errno)); + } + LOG_I(HW, + "SHM_RADIO: Realtime issues: TX %.2f%%, RX %.2f%%\n", + shm_radio_state->tx_samples_late / (float)shm_radio_state->tx_samples_total * 100, + shm_radio_state->rx_samples_late / (float)shm_radio_state->rx_samples_total * 100); + LOG_I(HW, + "SHM_RADIO: Read/write too early (suspected radio implementaton error) TX: %lu, RX: %lu\n", + shm_radio_state->tx_early, + shm_radio_state->rx_early); + LOG_I(HW, "SHM_RADIO: Average TX budget %.3lf uS\n", shm_radio_state->average_tx_budget); + shm_td_iq_channel_destroy(shm_radio_state->channel); +} + +static int shm_radio_stub(openair0_device *device) +{ + return 0; +} +static int shm_radio_stub2(openair0_device *device, openair0_config_t *openair0_cfg) +{ + return 0; +} + +__attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg) +{ + shm_radio_state_t *shm_radio_state = calloc(1, sizeof(shm_radio_state_t)); + shm_radio_readconfig(shm_radio_state); + LOG_I(HW, + "Running as %s\n", + shm_radio_state->role == ROLE_SERVER ? "server: waiting for client to connect" + : "client: will connect to a shm_radio server"); + device->trx_start_func = shm_radio_connect; + device->trx_reset_stats_func = shm_radio_stub; + device->trx_end_func = shm_radio_end; + device->trx_stop_func = shm_radio_stub; + device->trx_set_freq_func = shm_radio_stub2; + device->trx_set_gains_func = shm_radio_stub2; + device->trx_write_func = shm_radio_write; + device->trx_read_func = shm_radio_read; + + device->type = RFSIMULATOR; + device->openair0_cfg = &openair0_cfg[0]; + device->priv = shm_radio_state; + device->trx_write_init = shm_radio_stub; + shm_radio_state->last_received_sample = 0; + shm_radio_state->sample_rate = openair0_cfg->sample_rate; + return 0; +} From 551279119000c7b0b17459aa1cc7649e731bcf0e Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 27 Feb 2025 08:38:13 +0100 Subject: [PATCH 3/8] Add histogram for time budget printed at the exit of shm_radio --- radio/shm_radio/shm_radio.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/radio/shm_radio/shm_radio.c b/radio/shm_radio/shm_radio.c index 9962a996f7..6bb2c9b364 100644 --- a/radio/shm_radio/shm_radio.c +++ b/radio/shm_radio/shm_radio.c @@ -60,6 +60,13 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; }; // clang-format on +typedef struct histogram_s { + uint64_t diff[30]; + int num_samples; + int min_samples; + double range; +} histogram_t; + typedef struct { int role; char channel_name[40]; @@ -76,8 +83,29 @@ typedef struct { uint64_t rx_early; uint64_t rx_samples_total; double average_tx_budget; + histogram_t tx_histogram; } shm_radio_state_t; +static void histogram_add(histogram_t *histogram, double diff) +{ + histogram->num_samples++; + if (histogram->num_samples >= histogram->min_samples) { + int bin = min(sizeofArray(histogram->diff) - 1, max(0, (int)(diff / histogram->range * sizeofArray(histogram->diff)))); + histogram->diff[bin]++; + } +} + +static void histogram_print(histogram_t *histogram) +{ + LOG_I(HW, "SHM_RADIO: TX budget histogram: %d samples\n", histogram->num_samples); + float bin_size = histogram->range / sizeofArray(histogram->diff); + float bin_start = 0; + for (int i = 0; i < sizeofArray(histogram->diff); i++) { + LOG_I(HW, "Bin %d\t[%.1f - %.1fuS]:\t\t%lu\n", i, bin_start, bin_start + bin_size, histogram->diff[i]); + bin_start += bin_size; + } +} + static void shm_radio_readconfig(shm_radio_state_t *shm_radio_state) { char *channel_name = NULL; @@ -157,6 +185,7 @@ static int shm_radio_write(openair0_device *device, int64_t diff = timestamp - sample; double budget = diff / (shm_radio_state->sample_rate / 1e6); shm_radio_state->average_tx_budget = .05 * budget + .95 * shm_radio_state->average_tx_budget; + histogram_add(&shm_radio_state->tx_histogram, budget); int ret = shm_td_iq_channel_tx(shm_radio_state->channel, timestamp, nsamps, 0, samplesVoid[0]); if (ret == CHANNEL_ERROR_TOO_LATE) { @@ -201,6 +230,7 @@ static void shm_radio_end(openair0_device *device) shm_radio_state->tx_early, shm_radio_state->rx_early); LOG_I(HW, "SHM_RADIO: Average TX budget %.3lf uS\n", shm_radio_state->average_tx_budget); + histogram_print(&shm_radio_state->tx_histogram); shm_td_iq_channel_destroy(shm_radio_state->channel); } @@ -236,5 +266,8 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device *devi device->trx_write_init = shm_radio_stub; shm_radio_state->last_received_sample = 0; shm_radio_state->sample_rate = openair0_cfg->sample_rate; + shm_radio_state->tx_histogram.min_samples = 100; + // Set the histogram range to 3000uS. Anything above that is not interesting + shm_radio_state->tx_histogram.range = 3000.0; return 0; } From 4b61f14064bd8fbd1191b3281e839b045ed74dde Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 5 Mar 2025 13:36:35 +0100 Subject: [PATCH 4/8] shm_radio: Exchange information between peers This allows to exchange the number of antennas between client and server. This is necessary for channel modelling later. --- radio/shm_radio/shm_radio.c | 116 +++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/radio/shm_radio/shm_radio.c b/radio/shm_radio/shm_radio.c index 6bb2c9b364..c8779fc8a3 100644 --- a/radio/shm_radio/shm_radio.c +++ b/radio/shm_radio/shm_radio.c @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include @@ -67,6 +69,11 @@ typedef struct histogram_s { double range; } histogram_t; +// Information about the peer +typedef struct peer_info_s { + int num_rx_antennas; +} peer_info_t; + typedef struct { int role; char channel_name[40]; @@ -84,6 +91,7 @@ typedef struct { uint64_t rx_samples_total; double average_tx_budget; histogram_t tx_histogram; + peer_info_t peer_info; } shm_radio_state_t; static void histogram_add(histogram_t *histogram, double diff) @@ -154,11 +162,116 @@ static void *shm_radio_timing_job(void *arg) return 0; } +/** + * @brief Exchanges peer information between the server and the client. + * + * 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. + * + * @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. + */ +static peer_info_t server_exchange_peer_info(peer_info_t peer_info) +{ + int server_fd, client_fd; + struct sockaddr_un addr; + socklen_t addr_len = sizeof(addr); + + // 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 + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, "/tmp/shm_radio_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 + 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; +} + +/** + * @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) +{ + int client_fd; + struct sockaddr_un addr; + + // 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 + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, "/tmp/shm_radio_socket", sizeof(addr.sun_path) - 1); + int ret = -1; + int tries = 0; + while (ret != 0 && tries < 10) { + ret = connect(client_fd, (struct sockaddr *)&addr, sizeof(addr)); + tries++; + sleep(1); + } + 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; +} + static int shm_radio_connect(openair0_device *device) { shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv; + peer_info_t peer_info = {.num_rx_antennas = device->openair0_cfg[0].rx_num_channels}; if (shm_radio_state->role == ROLE_SERVER) { - shm_radio_state->channel = shm_td_iq_channel_create(shm_radio_state->channel_name, 1, 1); + shm_radio_state->peer_info = server_exchange_peer_info(peer_info); + shm_radio_state->channel = shm_td_iq_channel_create(shm_radio_state->channel_name, + shm_radio_state->peer_info.num_rx_antennas, + device->openair0_cfg[0].rx_num_channels); shm_radio_state->run_timing_thread = true; while (!shm_td_iq_channel_is_connected(shm_radio_state->channel)) { LOG_I(HW, "Waiting for client\n"); @@ -167,6 +280,7 @@ static int shm_radio_connect(openair0_device *device) int ret = pthread_create(&shm_radio_state->timing_thread, NULL, shm_radio_timing_job, shm_radio_state); AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno)); } else { + shm_radio_state->peer_info = client_exchange_peer_info(peer_info); shm_radio_state->channel = shm_td_iq_channel_connect(shm_radio_state->channel_name, 10); } return 0; From 5d3708ac3ee7dec979204b3ae19f56a3d3fb9722 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 5 Mar 2025 14:48:05 +0100 Subject: [PATCH 5/8] Realtime channel emulation for shm_radio --- common/utils/actor/CMakeLists.txt | 4 +- openair1/SIMULATION/TOOLS/sim.h | 1 + radio/shm_radio/CMakeLists.txt | 4 +- radio/shm_radio/README.md | 14 +- radio/shm_radio/noise_device.c | 90 +++++++++ radio/shm_radio/noise_device.h | 31 ++++ radio/shm_radio/shm_radio.c | 292 ++++++++++++++++++++++++++---- 7 files changed, 391 insertions(+), 45 deletions(-) create mode 100644 radio/shm_radio/noise_device.c create mode 100644 radio/shm_radio/noise_device.h diff --git a/common/utils/actor/CMakeLists.txt b/common/utils/actor/CMakeLists.txt index 40057d347e..182f54651d 100644 --- a/common/utils/actor/CMakeLists.txt +++ b/common/utils/actor/CMakeLists.txt @@ -1,6 +1,6 @@ add_library(actor actor.c) -target_include_directories(actor PUBLIC ./) -target_link_libraries(actor PUBLIC thread-pool) +# Only include header files due to issues with ODR in threadPool binaries +target_include_directories(actor PUBLIC ./ ../threadPool/) if (ENABLE_TESTS) add_subdirectory(tests) endif() diff --git a/openair1/SIMULATION/TOOLS/sim.h b/openair1/SIMULATION/TOOLS/sim.h index d6b20a9eb9..34cae4ba55 100644 --- a/openair1/SIMULATION/TOOLS/sim.h +++ b/openair1/SIMULATION/TOOLS/sim.h @@ -50,6 +50,7 @@ typedef enum { #define CHANMODEL_FREE_RSQRT_6 1<<1 #define CHANMODEL_FREE_RSQRT_NTAPS 1<<2 #define CHANMODEL_FREE_AMPS 1<<3 +#define SHR3 (jz = jsr, jsr ^= (jsr << 13), jsr ^= (jsr >> 17), jsr ^= (jsr << 5), jz + jsr) typedef enum { CORR_LEVEL_LOW, diff --git a/radio/shm_radio/CMakeLists.txt b/radio/shm_radio/CMakeLists.txt index dcaf3b6e1d..24f61cbd01 100644 --- a/radio/shm_radio/CMakeLists.txt +++ b/radio/shm_radio/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(shm_radio MODULE shm_radio.c) +add_library(shm_radio MODULE shm_radio.c noise_device.c) set_target_properties(shm_radio PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -target_link_libraries(shm_radio PRIVATE shm_td_iq_channel) +target_link_libraries(shm_radio PRIVATE SIMU shm_td_iq_channel actor) add_dependencies(shm_radio generate_T) diff --git a/radio/shm_radio/README.md b/radio/shm_radio/README.md index 6d66878110..fbe44c97bf 100644 --- a/radio/shm_radio/README.md +++ b/radio/shm_radio/README.md @@ -2,13 +2,14 @@ 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. +interface. # Limitations - - Only 1gNB to 1nrUE - - Only 1rx and 1tx antennas - - No channel modelling + - 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 @@ -17,3 +18,8 @@ On the UE and gNB: use `device.name shm_radio` command line argument. Additionally on gNB use `shm_radio.role server` and optionally `shm_radio.timescale ` to set the timescale. Timescale 1.0 is the default and means realtime. + +Channel modelling can be enabled by adding `shm_radio.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. diff --git a/radio/shm_radio/noise_device.c b/radio/shm_radio/noise_device.c new file mode 100644 index 0000000000..2e15944d4a --- /dev/null +++ b/radio/shm_radio/noise_device.c @@ -0,0 +1,90 @@ +/* + * 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 "noise_device.h" + +// Use block size equal to cache size +#define BLOCK_SIZE (64 / sizeof(float)) // 4 bytes per float +// Use a power of 2 for block size length +#define NUM_BLOCKS (8192) +#define NOISE_VECTOR_LENGTH (BLOCK_SIZE * NUM_BLOCKS) + +static struct { + pthread_t noise_device_thread; + float noise[NOISE_VECTOR_LENGTH] __attribute__((aligned(64))); + float noise_power; + bool running; +} state; + +static uint32_t jz, jsr = 123456789; +static uint32_t random_uint(void) +{ + return SHR3; +} + +static void generate_one_block_noise(int write_block_index, float noise_power) +{ + // Generate noise vector + float block[BLOCK_SIZE]; + for (int i = 0; i < BLOCK_SIZE; i++) { + block[i] = noise_power * gaussZiggurat(0, 1); + } + // Write block to noise vector + memcpy(state.noise + write_block_index * BLOCK_SIZE, block, sizeof(block)); +} + +void *noise_device_thread_function(void *arg) +{ + while (state.running) { + // Generate noise vector + generate_one_block_noise(random_uint() % NUM_BLOCKS, state.noise_power); + usleep(10000); + } + return NULL; +} + +void init_noise_device(float noise_power) +{ + for (int i = 0; i < NUM_BLOCKS; i++) { + generate_one_block_noise(i, noise_power); + } + int ret = pthread_create(&state.noise_device_thread, NULL, noise_device_thread_function, NULL); + AssertFatal(ret == 0, "pthread_create failed: %d, errno %d (%s)\n", ret, errno, strerror(errno)); +} + +void free_noise_device(void) +{ + state.running = false; + pthread_join(state.noise_device_thread, NULL); +} + +void get_noise_vector(float *noise_vector, int length) +{ + int start_block = random_uint() % NUM_BLOCKS; + while (length > 0) { + int copy_size = min(length, (NUM_BLOCKS - start_block) * BLOCK_SIZE); + memcpy(noise_vector, &state.noise[start_block * BLOCK_SIZE], copy_size * sizeof(float)); + start_block = 0; + length -= copy_size; + noise_vector += copy_size; + } +} diff --git a/radio/shm_radio/noise_device.h b/radio/shm_radio/noise_device.h new file mode 100644 index 0000000000..421723891a --- /dev/null +++ b/radio/shm_radio/noise_device.h @@ -0,0 +1,31 @@ +/* + * 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 NOISE_DEVICE_H +#define NOISE_DEVICE_H + +#include "SIMULATION/TOOLS/sim.h" + +void init_noise_device(float noise_power); +void free_noise_device(void); +void get_noise_vector(float *noise_vector, int length); + +#endif diff --git a/radio/shm_radio/shm_radio.c b/radio/shm_radio/shm_radio.c index c8779fc8a3..6dfa1179ee 100644 --- a/radio/shm_radio/shm_radio.c +++ b/radio/shm_radio/shm_radio.c @@ -42,10 +42,16 @@ #include #include "common_lib.h" #include "shm_td_iq_channel.h" +#include "SIMULATION/TOOLS/sim.h" +#include "actor.h" +#include "noise_device.h" +#include "simde/x86/avx512.h" // Simulator role typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; +#define NUM_CHANMOD_THREADS 1 + #define ROLE_CLIENT_STRING "client" #define ROLE_SERVER_STRING "server" @@ -59,6 +65,7 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; {"channel_name", "shared memory channel name\n", 0, .strptr = &channel_name, .defstrval = "shm_radio_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 = &shm_radio_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \ + {"chanmod", "Enable channel modelling", 0, .iptr = &shm_radio_state->chanmod, .defintval = 0, TYPE_INT, 0}, \ }; // clang-format on @@ -74,6 +81,14 @@ typedef struct peer_info_s { int num_rx_antennas; } peer_info_t; +typedef struct tx_timing_s { + uint64_t tx_samples_late; + uint64_t tx_early; + uint64_t tx_samples_total; + double average_tx_budget; + histogram_t tx_histogram; +} tx_timing_t; + typedef struct { int role; char channel_name[40]; @@ -83,15 +98,18 @@ typedef struct { bool run_timing_thread; double timescale; double sample_rate; - uint64_t tx_samples_late; - uint64_t tx_early; - uint64_t tx_samples_total; uint64_t rx_samples_late; uint64_t rx_early; uint64_t rx_samples_total; - double average_tx_budget; - histogram_t tx_histogram; + tx_timing_t *tx_timing; peer_info_t peer_info; + int chanmod; + double rx_freq; + double tx_bw; + int tx_num_channels; + int rx_num_channels; + channel_desc_t *channel_desc; + Actor_t *channel_modelling_actors; } shm_radio_state_t; static void histogram_add(histogram_t *histogram, double diff) @@ -114,6 +132,27 @@ static void histogram_print(histogram_t *histogram) } } +static void histogram_merge(histogram_t *dest, histogram_t *src) +{ + for (int i = 0; i < sizeofArray(dest->diff); i++) { + dest->diff[i] += src->diff[i]; + } + dest->num_samples += src->num_samples; +} + +static void load_channel_model(shm_radio_state_t *shm_radio_state) +{ + load_channellist(shm_radio_state->tx_num_channels, + shm_radio_state->peer_info.num_rx_antennas, + shm_radio_state->sample_rate, + shm_radio_state->rx_freq, + shm_radio_state->tx_bw); + char *model_name = shm_radio_state->role == ROLE_CLIENT ? "client_tx_channel_model" : "server_tx_channel_model"; + shm_radio_state->channel_desc = find_channel_desc_fromname(model_name); + random_channel(shm_radio_state->channel_desc, 0); + AssertFatal(shm_radio_state->channel_desc != NULL, "Could not find channel model %s\n", model_name); +} + static void shm_radio_readconfig(shm_radio_state_t *shm_radio_state) { char *channel_name = NULL; @@ -176,16 +215,13 @@ static void *shm_radio_timing_job(void *arg) static peer_info_t server_exchange_peer_info(peer_info_t peer_info) { int server_fd, client_fd; - struct sockaddr_un addr; - socklen_t addr_len = sizeof(addr); // 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 - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; + struct sockaddr_un addr = {.sun_family = AF_UNIX}; strncpy(addr.sun_path, "/tmp/shm_radio_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)); @@ -196,6 +232,7 @@ static peer_info_t server_exchange_peer_info(peer_info_t peer_info) 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)); @@ -229,15 +266,13 @@ static peer_info_t server_exchange_peer_info(peer_info_t peer_info) static peer_info_t client_exchange_peer_info(peer_info_t peer_info) { int client_fd; - struct sockaddr_un addr; // 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 - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; + struct sockaddr_un addr = {.sun_family = AF_UNIX}; strncpy(addr.sun_path, "/tmp/shm_radio_socket", sizeof(addr.sun_path) - 1); int ret = -1; int tries = 0; @@ -266,9 +301,33 @@ static peer_info_t client_exchange_peer_info(peer_info_t peer_info) static int shm_radio_connect(openair0_device *device) { shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv; + // Exchange peer info peer_info_t peer_info = {.num_rx_antennas = device->openair0_cfg[0].rx_num_channels}; if (shm_radio_state->role == ROLE_SERVER) { shm_radio_state->peer_info = server_exchange_peer_info(peer_info); + } else { + shm_radio_state->peer_info = client_exchange_peer_info(peer_info); + } + + // Handle channel modelling after number of RX antennas are known + int num_tx_stats = 1; + if (shm_radio_state->chanmod) { + shm_radio_state->channel_modelling_actors = calloc_or_fail(shm_radio_state->peer_info.num_rx_antennas, sizeof(Actor_t)); + for (int i = 0; i < shm_radio_state->peer_info.num_rx_antennas; i++) { + init_actor(&shm_radio_state->channel_modelling_actors[i], "chanmod", -1); + } + load_channel_model(shm_radio_state); + num_tx_stats = shm_radio_state->peer_info.num_rx_antennas; + } + shm_radio_state->tx_timing = calloc_or_fail(num_tx_stats, sizeof(tx_timing_t)); + for (int i = 0; i < num_tx_stats; i++) { + shm_radio_state->tx_timing[i].tx_histogram.min_samples = 100; + // Set the histogram range to 3000uS. Anything above that is not interesting + shm_radio_state->tx_timing[i].tx_histogram.range = 3000.0; + } + + // Setup a shared memory channel + if (shm_radio_state->role == ROLE_SERVER) { shm_radio_state->channel = shm_td_iq_channel_create(shm_radio_state->channel_name, shm_radio_state->peer_info.num_rx_antennas, device->openair0_cfg[0].rx_num_channels); @@ -280,12 +339,146 @@ static int shm_radio_connect(openair0_device *device) int ret = pthread_create(&shm_radio_state->timing_thread, NULL, shm_radio_timing_job, shm_radio_state); AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno)); } else { - shm_radio_state->peer_info = client_exchange_peer_info(peer_info); shm_radio_state->channel = shm_td_iq_channel_connect(shm_radio_state->channel_name, 10); } + return 0; } +static int shm_radio_write_internal(shm_radio_state_t *shm_radio_state, + openair0_timestamp timestamp, + c16_t *samples, + int nsamps, + int aarx, + int flags, + int stats_index) +{ + tx_timing_t *tx_timing = &shm_radio_state->tx_timing[stats_index]; + + uint64_t sample = shm_td_iq_channel_get_current_sample(shm_radio_state->channel); + int64_t diff = timestamp - sample; + double budget = diff / (shm_radio_state->sample_rate / 1e6); + tx_timing->average_tx_budget = .05 * budget + .95 * tx_timing->average_tx_budget; + histogram_add(&tx_timing->tx_histogram, budget); + + int ret = shm_td_iq_channel_tx(shm_radio_state->channel, timestamp, nsamps, aarx, (sample_t *)samples); + + if (ret == CHANNEL_ERROR_TOO_LATE) { + tx_timing->tx_samples_late += nsamps; + } else if (ret == CHANNEL_ERROR_TOO_EARLY) { + tx_timing->tx_early += 1; + } + tx_timing->tx_samples_total += nsamps; + + return nsamps; +} + +typedef struct { + shm_radio_state_t *shm_radio_state; + openair0_timestamp timestamp; + c16_t *samples[4]; + int nsamps; + int nbAnt; + int flags; + int aarx; +} channel_modelling_args_t; + +static void perform_channel_modelling(void *arg) +{ + channel_modelling_args_t *channel_modelling_args = arg; + int nsamps = channel_modelling_args->nsamps; + int aarx = channel_modelling_args->aarx; + int nb_tx_ant = channel_modelling_args->nbAnt; + c16_t **input_samples = (c16_t **)channel_modelling_args->samples; + + int aligned_nsamps = ceil_mod(nsamps, (512 / 8) / sizeof(cf_t)); + cf_t samples[aligned_nsamps] __attribute__((aligned(64))); + // Apply noise from global settings + get_noise_vector((float *)samples, nsamps * 2); + + channel_desc_t *channel_desc = channel_modelling_args->shm_radio_state->channel_desc; + const float pathloss_linear = powf(10, channel_desc->path_loss_dB / 20.0); + + // 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; + } + } + + for (int aatx = 0; aatx < nb_tx_ant; 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; + } + } + } + } + + // Convert to c16_t + c16_t samples_out[aligned_nsamps] __attribute__((aligned(64))); +#if defined(__AVX512F__) + for (int i = 0; i < aligned_nsamps / 8; i++) { + simde__m512 *in = (simde__m512 *)&samples[i * 8]; + simde__m256i *out = (simde__m256i *)&samples_out[i * 8]; + *out = simde_mm512_cvtsepi32_epi16(simde_mm512_cvtps_epi32(*in)); + } +#elif defined(__AVX2__) + for (int i = 0; i < aligned_nsamps / 4; i++) { + simde__m256 *in = (simde__m256 *)&samples[i * 4]; + simde__m128i *out = (simde__m128i *)&samples_out[i * 4]; + *out = simde_mm256_cvtsepi32_epi16(simde_mm256_cvtps_epi32(*in)); + } +#else + for (int i = 0; i < nsamps; i++) { + samples_out[i].r = lroundf(samples[i].r); + samples_out[i].i = lroundf(samples[i].i); + } +#endif + + shm_radio_write_internal(channel_modelling_args->shm_radio_state, + channel_modelling_args->timestamp, + samples_out, + channel_modelling_args->nsamps, + aarx, + channel_modelling_args->flags, + aarx); +} + +static int shm_radio_write_with_chanmod(shm_radio_state_t *shm_radio_state, + openair0_timestamp timestamp, + void **samplesVoid, + int nsamps, + int nbAnt, + int flags) +{ + for (int aarx = 0; aarx < shm_radio_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); + args->shm_radio_state = shm_radio_state; + args->timestamp = timestamp; + args->nsamps = nsamps; + args->nbAnt = nbAnt; + args->flags = flags; + args->aarx = aarx; + for (int i = 0; i < nbAnt; i++) { + args->samples[i] = samplesVoid[i]; + } + pushNotifiedFIFO(&shm_radio_state->channel_modelling_actors[aarx].fifo, task); + } + return nsamps; +} + static int shm_radio_write(openair0_device *device, openair0_timestamp timestamp, void **samplesVoid, @@ -295,20 +488,9 @@ static int shm_radio_write(openair0_device *device, { timestamp -= device->openair0_cfg->command_line_sample_advance; shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv; - uint64_t sample = shm_td_iq_channel_get_current_sample(shm_radio_state->channel); - int64_t diff = timestamp - sample; - double budget = diff / (shm_radio_state->sample_rate / 1e6); - shm_radio_state->average_tx_budget = .05 * budget + .95 * shm_radio_state->average_tx_budget; - histogram_add(&shm_radio_state->tx_histogram, budget); - - int ret = shm_td_iq_channel_tx(shm_radio_state->channel, timestamp, nsamps, 0, samplesVoid[0]); - if (ret == CHANNEL_ERROR_TOO_LATE) { - shm_radio_state->tx_samples_late += nsamps; - } else if (ret == CHANNEL_ERROR_TOO_EARLY) { - shm_radio_state->tx_early += 1; - } - shm_radio_state->tx_samples_total += nsamps; - return nsamps; + return shm_radio_state->chanmod + ? shm_radio_write_with_chanmod(shm_radio_state, timestamp, samplesVoid, nsamps, nbAnt, flags) + : shm_radio_write_internal(shm_radio_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0); } static int shm_radio_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) @@ -335,17 +517,38 @@ static void shm_radio_end(openair0_device *device) int ret = pthread_join(shm_radio_state->timing_thread, NULL); AssertFatal(ret == 0, "pthread_join() failed: errno: %d, %s\n", errno, strerror(errno)); } + + tx_timing_t *tx_timing = shm_radio_state->tx_timing; + if (shm_radio_state->chanmod) { + for (int i = 0; i < shm_radio_state->peer_info.num_rx_antennas; i++) { + shutdown_actor(&shm_radio_state->channel_modelling_actors[i]); + } + free(shm_radio_state->channel_modelling_actors); + for (int i = 1; i < shm_radio_state->peer_info.num_rx_antennas; i++) { + histogram_merge(&tx_timing->tx_histogram, &tx_timing[i].tx_histogram); + tx_timing->tx_early += tx_timing[i].tx_early; + tx_timing->tx_samples_late += tx_timing[i].tx_samples_late; + tx_timing->average_tx_budget += tx_timing[i].average_tx_budget; + tx_timing->tx_samples_total += tx_timing[i].tx_samples_total; + } + tx_timing->average_tx_budget /= shm_radio_state->peer_info.num_rx_antennas; + } + // produce 1 second of extra samples so threads can finish + shm_td_iq_channel_produce_samples(shm_radio_state->channel, shm_radio_state->sample_rate); + sleep(1); + shm_td_iq_channel_destroy(shm_radio_state->channel); + LOG_I(HW, "SHM_RADIO: Realtime issues: TX %.2f%%, RX %.2f%%\n", - shm_radio_state->tx_samples_late / (float)shm_radio_state->tx_samples_total * 100, + tx_timing->tx_samples_late / (float)tx_timing->tx_samples_total * 100, shm_radio_state->rx_samples_late / (float)shm_radio_state->rx_samples_total * 100); LOG_I(HW, "SHM_RADIO: Read/write too early (suspected radio implementaton error) TX: %lu, RX: %lu\n", - shm_radio_state->tx_early, + tx_timing->tx_early, shm_radio_state->rx_early); - LOG_I(HW, "SHM_RADIO: Average TX budget %.3lf uS\n", shm_radio_state->average_tx_budget); - histogram_print(&shm_radio_state->tx_histogram); - shm_td_iq_channel_destroy(shm_radio_state->channel); + LOG_I(HW, "SHM_RADIO: Average TX budget %.3lf uS\n", tx_timing->average_tx_budget); + histogram_print(&tx_timing->tx_histogram); + free(shm_radio_state->tx_timing); } static int shm_radio_stub(openair0_device *device) @@ -357,9 +560,16 @@ static int shm_radio_stub2(openair0_device *device, openair0_config_t *openair0_ return 0; } +static int shm_radio_set_freq(openair0_device *device, openair0_config_t *openair0_cfg) +{ + shm_radio_state_t *s = device->priv; + s->rx_freq = openair0_cfg->rx_freq[0]; + return 0; +} + __attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { - shm_radio_state_t *shm_radio_state = calloc(1, sizeof(shm_radio_state_t)); + shm_radio_state_t *shm_radio_state = calloc_or_fail(1, sizeof(shm_radio_state_t)); shm_radio_readconfig(shm_radio_state); LOG_I(HW, "Running as %s\n", @@ -369,7 +579,7 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device *devi device->trx_reset_stats_func = shm_radio_stub; device->trx_end_func = shm_radio_end; device->trx_stop_func = shm_radio_stub; - device->trx_set_freq_func = shm_radio_stub2; + device->trx_set_freq_func = shm_radio_set_freq; device->trx_set_gains_func = shm_radio_stub2; device->trx_write_func = shm_radio_write; device->trx_read_func = shm_radio_read; @@ -380,8 +590,16 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device *devi device->trx_write_init = shm_radio_stub; shm_radio_state->last_received_sample = 0; shm_radio_state->sample_rate = openair0_cfg->sample_rate; - shm_radio_state->tx_histogram.min_samples = 100; - // Set the histogram range to 3000uS. Anything above that is not interesting - shm_radio_state->tx_histogram.range = 3000.0; + shm_radio_state->rx_freq = openair0_cfg->rx_freq[0]; + shm_radio_state->tx_bw = openair0_cfg->tx_bw; + shm_radio_state->tx_num_channels = openair0_cfg->tx_num_channels; + shm_radio_state->rx_num_channels = openair0_cfg->rx_num_channels; + + if (shm_radio_state->chanmod) { + 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)); + init_noise_device(noise_power); + } return 0; } From c5c2e8781091d3e7f832e8f785a87c949433b701 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 26 Mar 2025 15:32:59 +0100 Subject: [PATCH 6/8] Rename shm_radio to vrtsim --- doc/README.md | 2 +- radio/CMakeLists.txt | 6 +- radio/shm_radio/CMakeLists.txt | 4 - radio/vrtsim/CMakeLists.txt | 4 + radio/{shm_radio => vrtsim}/README.md | 8 +- radio/{shm_radio => vrtsim}/noise_device.c | 0 radio/{shm_radio => vrtsim}/noise_device.h | 0 .../shm_radio.c => vrtsim/vrtsim.c} | 283 +++++++++--------- 8 files changed, 150 insertions(+), 157 deletions(-) delete mode 100644 radio/shm_radio/CMakeLists.txt create mode 100644 radio/vrtsim/CMakeLists.txt rename radio/{shm_radio => vrtsim}/README.md (69%) rename radio/{shm_radio => vrtsim}/noise_device.c (100%) rename radio/{shm_radio => vrtsim}/noise_device.h (100%) rename radio/{shm_radio/shm_radio.c => vrtsim/vrtsim.c} (61%) diff --git a/doc/README.md b/doc/README.md index 30e0116abf..cee2620f95 100644 --- a/doc/README.md +++ b/doc/README.md @@ -118,7 +118,7 @@ Some directories under `radio` contain READMEs: - [BladeRF](../radio/BLADERF/README) - [IQPlayer](../radio/iqplayer/DOC/iqrecordplayer_usage.md), and [general documentation](./iqrecordplayer_usage.md) - [fhi_72](../radio/fhi_72/README.md) -- [shm_radio](../radio/shm_radio/README.md) +- [vrtsim](../radio/vrtsim/README.md) The other SDRs (AW2S, LimeSDR, ...) have no READMEs. diff --git a/radio/CMakeLists.txt b/radio/CMakeLists.txt index a2da0ac616..654fb037f0 100644 --- a/radio/CMakeLists.txt +++ b/radio/CMakeLists.txt @@ -38,7 +38,7 @@ if(OAI_USRP) add_subdirectory(USRP) endif() -add_boolean_option(OAI_SHM_RADIO ON "Activate OAI's shared memory radio driver" OFF) -if(OAI_SHM_RADIO) - add_subdirectory(shm_radio) +add_boolean_option(VRTSIM_RADIO ON "Activate OAI's shared memory radio driver" OFF) +if(VRTSIM_RADIO) + add_subdirectory(vrtsim) endif() diff --git a/radio/shm_radio/CMakeLists.txt b/radio/shm_radio/CMakeLists.txt deleted file mode 100644 index 24f61cbd01..0000000000 --- a/radio/shm_radio/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_library(shm_radio MODULE shm_radio.c noise_device.c) -set_target_properties(shm_radio PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -target_link_libraries(shm_radio PRIVATE SIMU shm_td_iq_channel actor) -add_dependencies(shm_radio generate_T) diff --git a/radio/vrtsim/CMakeLists.txt b/radio/vrtsim/CMakeLists.txt new file mode 100644 index 0000000000..79eb4d88da --- /dev/null +++ b/radio/vrtsim/CMakeLists.txt @@ -0,0 +1,4 @@ +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) diff --git a/radio/shm_radio/README.md b/radio/vrtsim/README.md similarity index 69% rename from radio/shm_radio/README.md rename to radio/vrtsim/README.md index fbe44c97bf..71455175c3 100644 --- a/radio/shm_radio/README.md +++ b/radio/vrtsim/README.md @@ -13,13 +13,13 @@ interface. # Usage -On the UE and gNB: use `device.name shm_radio` command line argument. +On the UE and gNB: use `device.name vrtsim` command line argument. -Additionally on gNB use `shm_radio.role server` and optionally -`shm_radio.timescale ` to set the timescale. Timescale 1.0 +Additionally on gNB use `vrtsim.role server` and optionally +`vrtsim.timescale ` to set the timescale. Timescale 1.0 is the default and means realtime. -Channel modelling can be enabled by adding `shm_radio.chanmod 1` to the +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. diff --git a/radio/shm_radio/noise_device.c b/radio/vrtsim/noise_device.c similarity index 100% rename from radio/shm_radio/noise_device.c rename to radio/vrtsim/noise_device.c diff --git a/radio/shm_radio/noise_device.h b/radio/vrtsim/noise_device.h similarity index 100% rename from radio/shm_radio/noise_device.h rename to radio/vrtsim/noise_device.h diff --git a/radio/shm_radio/shm_radio.c b/radio/vrtsim/vrtsim.c similarity index 61% rename from radio/shm_radio/shm_radio.c rename to radio/vrtsim/vrtsim.c index 6dfa1179ee..f0e0151aa6 100644 --- a/radio/shm_radio/shm_radio.c +++ b/radio/vrtsim/vrtsim.c @@ -55,17 +55,17 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; #define ROLE_CLIENT_STRING "client" #define ROLE_SERVER_STRING "server" -#define SHM_RADIO_SECTION "shm_radio" +#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" // clang-format off -#define SHM_RADIO_PARAMS_DESC \ +#define VRTSIM_PARAMS_DESC \ { \ - {"channel_name", "shared memory channel name\n", 0, .strptr = &channel_name, .defstrval = "shm_radio_channel", TYPE_STRING, 0}, \ + {"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 = &shm_radio_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \ - {"chanmod", "Enable channel modelling", 0, .iptr = &shm_radio_state->chanmod, .defintval = 0, TYPE_INT, 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}, \ }; // clang-format on @@ -110,7 +110,7 @@ typedef struct { int rx_num_channels; channel_desc_t *channel_desc; Actor_t *channel_modelling_actors; -} shm_radio_state_t; +} vrtsim_state_t; static void histogram_add(histogram_t *histogram, double diff) { @@ -123,7 +123,7 @@ static void histogram_add(histogram_t *histogram, double diff) static void histogram_print(histogram_t *histogram) { - LOG_I(HW, "SHM_RADIO: TX budget histogram: %d samples\n", histogram->num_samples); + LOG_I(HW, "VRTSIM: TX budget histogram: %d samples\n", histogram->num_samples); float bin_size = histogram->range / sizeofArray(histogram->diff); float bin_start = 0; for (int i = 0; i < sizeofArray(histogram->diff); i++) { @@ -140,46 +140,46 @@ static void histogram_merge(histogram_t *dest, histogram_t *src) dest->num_samples += src->num_samples; } -static void load_channel_model(shm_radio_state_t *shm_radio_state) +static void load_channel_model(vrtsim_state_t *vrtsim_state) { - load_channellist(shm_radio_state->tx_num_channels, - shm_radio_state->peer_info.num_rx_antennas, - shm_radio_state->sample_rate, - shm_radio_state->rx_freq, - shm_radio_state->tx_bw); - char *model_name = shm_radio_state->role == ROLE_CLIENT ? "client_tx_channel_model" : "server_tx_channel_model"; - shm_radio_state->channel_desc = find_channel_desc_fromname(model_name); - random_channel(shm_radio_state->channel_desc, 0); - AssertFatal(shm_radio_state->channel_desc != NULL, "Could not find channel model %s\n", model_name); + load_channellist(vrtsim_state->tx_num_channels, + vrtsim_state->peer_info.num_rx_antennas, + vrtsim_state->sample_rate, + vrtsim_state->rx_freq, + 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); + random_channel(vrtsim_state->channel_desc, 0); + AssertFatal(vrtsim_state->channel_desc != NULL, "Could not find channel model %s\n", model_name); } -static void shm_radio_readconfig(shm_radio_state_t *shm_radio_state) +static void vrtsim_readconfig(vrtsim_state_t *vrtsim_state) { char *channel_name = NULL; char *role = NULL; - paramdef_t shm_radio_params[] = SHM_RADIO_PARAMS_DESC; - int ret = config_get(config_get_if(), shm_radio_params, sizeofArray(shm_radio_params), SHM_RADIO_SECTION); + 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(shm_radio_state->channel_name, channel_name, sizeof(shm_radio_state->channel_name) - 1); + strncpy(vrtsim_state->channel_name, channel_name, sizeof(vrtsim_state->channel_name) - 1); if (strncmp(role, ROLE_CLIENT_STRING, strlen(ROLE_CLIENT_STRING)) == 0) { - shm_radio_state->role = ROLE_CLIENT; + vrtsim_state->role = ROLE_CLIENT; } else if (strncmp(role, ROLE_SERVER_STRING, strlen(ROLE_SERVER_STRING)) == 0) { - shm_radio_state->role = ROLE_SERVER; + vrtsim_state->role = ROLE_SERVER; } else { AssertFatal(false, "Invalid role configuration\n"); } } -static void *shm_radio_timing_job(void *arg) +static void *vrtsim_timing_job(void *arg) { - shm_radio_state_t *shm_radio_state = arg; + vrtsim_state_t *vrtsim_state = arg; struct timespec timestamp; if (clock_gettime(CLOCK_REALTIME, ×tamp)) { LOG_E(UTIL, "clock_gettime failed\n"); exit(1); } double leftover_samples = 0; - while (shm_radio_state->run_timing_thread) { + while (vrtsim_state->run_timing_thread) { struct timespec current_time; if (clock_gettime(CLOCK_REALTIME, ¤t_time)) { LOG_E(UTIL, "clock_gettime failed\n"); @@ -187,7 +187,7 @@ static void *shm_radio_timing_job(void *arg) } uint64_t diff = (current_time.tv_sec - timestamp.tv_sec) * 1000000000 + (current_time.tv_nsec - timestamp.tv_nsec); timestamp = current_time; - double samples_to_produce = shm_radio_state->sample_rate * shm_radio_state->timescale * diff / 1e9; + double samples_to_produce = vrtsim_state->sample_rate * vrtsim_state->timescale * diff / 1e9; // Attempt to correct compounding rounding error leftover_samples += samples_to_produce - (uint64_t)samples_to_produce; @@ -195,7 +195,7 @@ static void *shm_radio_timing_job(void *arg) samples_to_produce += 1; leftover_samples -= 1; } - shm_td_iq_channel_produce_samples(shm_radio_state->channel, samples_to_produce); + shm_td_iq_channel_produce_samples(vrtsim_state->channel, samples_to_produce); usleep(1); } return 0; @@ -222,7 +222,7 @@ static peer_info_t server_exchange_peer_info(peer_info_t peer_info) // Bind the socket to a file path struct sockaddr_un addr = {.sun_family = AF_UNIX}; - strncpy(addr.sun_path, "/tmp/shm_radio_socket", sizeof(addr.sun_path) - 1); + 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)); @@ -273,7 +273,7 @@ static peer_info_t client_exchange_peer_info(peer_info_t peer_info) // Connect to the server struct sockaddr_un addr = {.sun_family = AF_UNIX}; - strncpy(addr.sun_path, "/tmp/shm_radio_socket", sizeof(addr.sun_path) - 1); + strncpy(addr.sun_path, "/tmp/vrtsim_socket", sizeof(addr.sun_path) - 1); int ret = -1; int tries = 0; while (ret != 0 && tries < 10) { @@ -298,70 +298,70 @@ static peer_info_t client_exchange_peer_info(peer_info_t peer_info) return peer_info_received; } -static int shm_radio_connect(openair0_device *device) +static int vrtsim_connect(openair0_device *device) { - shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv; + 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}; - if (shm_radio_state->role == ROLE_SERVER) { - shm_radio_state->peer_info = server_exchange_peer_info(peer_info); + if (vrtsim_state->role == ROLE_SERVER) { + vrtsim_state->peer_info = server_exchange_peer_info(peer_info); } else { - shm_radio_state->peer_info = client_exchange_peer_info(peer_info); + vrtsim_state->peer_info = client_exchange_peer_info(peer_info); } // Handle channel modelling after number of RX antennas are known int num_tx_stats = 1; - if (shm_radio_state->chanmod) { - shm_radio_state->channel_modelling_actors = calloc_or_fail(shm_radio_state->peer_info.num_rx_antennas, sizeof(Actor_t)); - for (int i = 0; i < shm_radio_state->peer_info.num_rx_antennas; i++) { - init_actor(&shm_radio_state->channel_modelling_actors[i], "chanmod", -1); + if (vrtsim_state->chanmod) { + 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(shm_radio_state); - num_tx_stats = shm_radio_state->peer_info.num_rx_antennas; + load_channel_model(vrtsim_state); + num_tx_stats = vrtsim_state->peer_info.num_rx_antennas; } - shm_radio_state->tx_timing = calloc_or_fail(num_tx_stats, sizeof(tx_timing_t)); + vrtsim_state->tx_timing = calloc_or_fail(num_tx_stats, sizeof(tx_timing_t)); for (int i = 0; i < num_tx_stats; i++) { - shm_radio_state->tx_timing[i].tx_histogram.min_samples = 100; + vrtsim_state->tx_timing[i].tx_histogram.min_samples = 100; // Set the histogram range to 3000uS. Anything above that is not interesting - shm_radio_state->tx_timing[i].tx_histogram.range = 3000.0; + vrtsim_state->tx_timing[i].tx_histogram.range = 3000.0; } // Setup a shared memory channel - if (shm_radio_state->role == ROLE_SERVER) { - shm_radio_state->channel = shm_td_iq_channel_create(shm_radio_state->channel_name, - shm_radio_state->peer_info.num_rx_antennas, - device->openair0_cfg[0].rx_num_channels); - shm_radio_state->run_timing_thread = true; - while (!shm_td_iq_channel_is_connected(shm_radio_state->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; + while (!shm_td_iq_channel_is_connected(vrtsim_state->channel)) { LOG_I(HW, "Waiting for client\n"); sleep(1); } - int ret = pthread_create(&shm_radio_state->timing_thread, NULL, shm_radio_timing_job, shm_radio_state); + 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 { - shm_radio_state->channel = shm_td_iq_channel_connect(shm_radio_state->channel_name, 10); + vrtsim_state->channel = shm_td_iq_channel_connect(vrtsim_state->channel_name, 10); } return 0; } -static int shm_radio_write_internal(shm_radio_state_t *shm_radio_state, - openair0_timestamp timestamp, - c16_t *samples, - int nsamps, - int aarx, - int flags, - int stats_index) +static int vrtsim_write_internal(vrtsim_state_t *vrtsim_state, + openair0_timestamp timestamp, + c16_t *samples, + int nsamps, + int aarx, + int flags, + int stats_index) { - tx_timing_t *tx_timing = &shm_radio_state->tx_timing[stats_index]; + tx_timing_t *tx_timing = &vrtsim_state->tx_timing[stats_index]; - uint64_t sample = shm_td_iq_channel_get_current_sample(shm_radio_state->channel); + uint64_t sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel); int64_t diff = timestamp - sample; - double budget = diff / (shm_radio_state->sample_rate / 1e6); + double budget = diff / (vrtsim_state->sample_rate / 1e6); tx_timing->average_tx_budget = .05 * budget + .95 * tx_timing->average_tx_budget; histogram_add(&tx_timing->tx_histogram, budget); - int ret = shm_td_iq_channel_tx(shm_radio_state->channel, timestamp, nsamps, aarx, (sample_t *)samples); + int ret = shm_td_iq_channel_tx(vrtsim_state->channel, timestamp, nsamps, aarx, (sample_t *)samples); if (ret == CHANNEL_ERROR_TOO_LATE) { tx_timing->tx_samples_late += nsamps; @@ -374,7 +374,7 @@ static int shm_radio_write_internal(shm_radio_state_t *shm_radio_state, } typedef struct { - shm_radio_state_t *shm_radio_state; + vrtsim_state_t *vrtsim_state; openair0_timestamp timestamp; c16_t *samples[4]; int nsamps; @@ -396,7 +396,7 @@ 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->shm_radio_state->channel_desc; + 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); // Convert channel impulse response to float + apply pathloss @@ -446,26 +446,26 @@ static void perform_channel_modelling(void *arg) } #endif - shm_radio_write_internal(channel_modelling_args->shm_radio_state, - channel_modelling_args->timestamp, - samples_out, - channel_modelling_args->nsamps, - aarx, - channel_modelling_args->flags, - aarx); + vrtsim_write_internal(channel_modelling_args->vrtsim_state, + channel_modelling_args->timestamp, + samples_out, + channel_modelling_args->nsamps, + aarx, + channel_modelling_args->flags, + aarx); } -static int shm_radio_write_with_chanmod(shm_radio_state_t *shm_radio_state, - openair0_timestamp timestamp, - void **samplesVoid, - int nsamps, - int nbAnt, - int flags) +static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state, + openair0_timestamp timestamp, + void **samplesVoid, + int nsamps, + int nbAnt, + int flags) { - for (int aarx = 0; aarx < shm_radio_state->peer_info.num_rx_antennas; aarx++) { + 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); - args->shm_radio_state = shm_radio_state; + args->vrtsim_state = vrtsim_state; args->timestamp = timestamp; args->nsamps = nsamps; args->nbAnt = nbAnt; @@ -474,128 +474,121 @@ static int shm_radio_write_with_chanmod(shm_radio_state_t *shm_radio_state, for (int i = 0; i < nbAnt; i++) { args->samples[i] = samplesVoid[i]; } - pushNotifiedFIFO(&shm_radio_state->channel_modelling_actors[aarx].fifo, task); + pushNotifiedFIFO(&vrtsim_state->channel_modelling_actors[aarx].fifo, task); } return nsamps; } -static int shm_radio_write(openair0_device *device, - openair0_timestamp timestamp, - void **samplesVoid, - int nsamps, - int nbAnt, - int flags) +static int vrtsim_write(openair0_device *device, openair0_timestamp timestamp, void **samplesVoid, int nsamps, int nbAnt, int flags) { timestamp -= device->openair0_cfg->command_line_sample_advance; - shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv; - return shm_radio_state->chanmod - ? shm_radio_write_with_chanmod(shm_radio_state, timestamp, samplesVoid, nsamps, nbAnt, flags) - : shm_radio_write_internal(shm_radio_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0); + 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); } -static int shm_radio_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) +static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) { - shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv; - shm_td_iq_channel_wait(shm_radio_state->channel, shm_radio_state->last_received_sample + nsamps); - int ret = shm_td_iq_channel_rx(shm_radio_state->channel, shm_radio_state->last_received_sample, nsamps, 0, samplesVoid[0]); + vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; + shm_td_iq_channel_wait(vrtsim_state->channel, vrtsim_state->last_received_sample + nsamps); + 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) { - shm_radio_state->rx_samples_late += nsamps; + vrtsim_state->rx_samples_late += nsamps; } else if (ret == CHANNEL_ERROR_TOO_EARLY) { - shm_radio_state->rx_early += 1; + vrtsim_state->rx_early += 1; } - shm_radio_state->rx_samples_total += nsamps; - *ptimestamp = shm_radio_state->last_received_sample; - shm_radio_state->last_received_sample += nsamps; + vrtsim_state->rx_samples_total += nsamps; + *ptimestamp = vrtsim_state->last_received_sample; + vrtsim_state->last_received_sample += nsamps; return nsamps; } -static void shm_radio_end(openair0_device *device) +static void vrtsim_end(openair0_device *device) { - shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv; - if (shm_radio_state->role == ROLE_SERVER) { - shm_radio_state->run_timing_thread = false; - int ret = pthread_join(shm_radio_state->timing_thread, NULL); + vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; + if (vrtsim_state->role == ROLE_SERVER) { + 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)); } - tx_timing_t *tx_timing = shm_radio_state->tx_timing; - if (shm_radio_state->chanmod) { - for (int i = 0; i < shm_radio_state->peer_info.num_rx_antennas; i++) { - shutdown_actor(&shm_radio_state->channel_modelling_actors[i]); + tx_timing_t *tx_timing = vrtsim_state->tx_timing; + if (vrtsim_state->chanmod) { + for (int i = 0; i < vrtsim_state->peer_info.num_rx_antennas; i++) { + shutdown_actor(&vrtsim_state->channel_modelling_actors[i]); } - free(shm_radio_state->channel_modelling_actors); - for (int i = 1; i < shm_radio_state->peer_info.num_rx_antennas; i++) { + free(vrtsim_state->channel_modelling_actors); + for (int i = 1; i < vrtsim_state->peer_info.num_rx_antennas; i++) { histogram_merge(&tx_timing->tx_histogram, &tx_timing[i].tx_histogram); tx_timing->tx_early += tx_timing[i].tx_early; tx_timing->tx_samples_late += tx_timing[i].tx_samples_late; tx_timing->average_tx_budget += tx_timing[i].average_tx_budget; tx_timing->tx_samples_total += tx_timing[i].tx_samples_total; } - tx_timing->average_tx_budget /= shm_radio_state->peer_info.num_rx_antennas; + tx_timing->average_tx_budget /= vrtsim_state->peer_info.num_rx_antennas; } // produce 1 second of extra samples so threads can finish - shm_td_iq_channel_produce_samples(shm_radio_state->channel, shm_radio_state->sample_rate); + shm_td_iq_channel_produce_samples(vrtsim_state->channel, vrtsim_state->sample_rate); sleep(1); - shm_td_iq_channel_destroy(shm_radio_state->channel); + shm_td_iq_channel_destroy(vrtsim_state->channel); LOG_I(HW, - "SHM_RADIO: Realtime issues: TX %.2f%%, RX %.2f%%\n", + "VRTSIM: Realtime issues: TX %.2f%%, RX %.2f%%\n", tx_timing->tx_samples_late / (float)tx_timing->tx_samples_total * 100, - shm_radio_state->rx_samples_late / (float)shm_radio_state->rx_samples_total * 100); + vrtsim_state->rx_samples_late / (float)vrtsim_state->rx_samples_total * 100); LOG_I(HW, - "SHM_RADIO: Read/write too early (suspected radio implementaton error) TX: %lu, RX: %lu\n", + "VRTSIM: Read/write too early (suspected radio implementaton error) TX: %lu, RX: %lu\n", tx_timing->tx_early, - shm_radio_state->rx_early); - LOG_I(HW, "SHM_RADIO: Average TX budget %.3lf uS\n", tx_timing->average_tx_budget); + vrtsim_state->rx_early); + LOG_I(HW, "VRTSIM: Average TX budget %.3lf uS\n", tx_timing->average_tx_budget); histogram_print(&tx_timing->tx_histogram); - free(shm_radio_state->tx_timing); + free(vrtsim_state->tx_timing); } -static int shm_radio_stub(openair0_device *device) +static int vrtsim_stub(openair0_device *device) { return 0; } -static int shm_radio_stub2(openair0_device *device, openair0_config_t *openair0_cfg) +static int vrtsim_stub2(openair0_device *device, openair0_config_t *openair0_cfg) { return 0; } -static int shm_radio_set_freq(openair0_device *device, openair0_config_t *openair0_cfg) +static int vrtsim_set_freq(openair0_device *device, openair0_config_t *openair0_cfg) { - shm_radio_state_t *s = device->priv; + vrtsim_state_t *s = device->priv; s->rx_freq = openair0_cfg->rx_freq[0]; return 0; } __attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { - shm_radio_state_t *shm_radio_state = calloc_or_fail(1, sizeof(shm_radio_state_t)); - shm_radio_readconfig(shm_radio_state); + vrtsim_state_t *vrtsim_state = calloc_or_fail(1, sizeof(vrtsim_state_t)); + vrtsim_readconfig(vrtsim_state); LOG_I(HW, "Running as %s\n", - shm_radio_state->role == ROLE_SERVER ? "server: waiting for client to connect" - : "client: will connect to a shm_radio server"); - device->trx_start_func = shm_radio_connect; - device->trx_reset_stats_func = shm_radio_stub; - device->trx_end_func = shm_radio_end; - device->trx_stop_func = shm_radio_stub; - device->trx_set_freq_func = shm_radio_set_freq; - device->trx_set_gains_func = shm_radio_stub2; - device->trx_write_func = shm_radio_write; - device->trx_read_func = shm_radio_read; + vrtsim_state->role == ROLE_SERVER ? "server: waiting for client to connect" : "client: will connect to a vrtsim server"); + device->trx_start_func = vrtsim_connect; + device->trx_reset_stats_func = vrtsim_stub; + device->trx_end_func = vrtsim_end; + device->trx_stop_func = vrtsim_stub; + device->trx_set_freq_func = vrtsim_set_freq; + device->trx_set_gains_func = vrtsim_stub2; + device->trx_write_func = vrtsim_write; + device->trx_read_func = vrtsim_read; device->type = RFSIMULATOR; device->openair0_cfg = &openair0_cfg[0]; - device->priv = shm_radio_state; - device->trx_write_init = shm_radio_stub; - shm_radio_state->last_received_sample = 0; - shm_radio_state->sample_rate = openair0_cfg->sample_rate; - shm_radio_state->rx_freq = openair0_cfg->rx_freq[0]; - shm_radio_state->tx_bw = openair0_cfg->tx_bw; - shm_radio_state->tx_num_channels = openair0_cfg->tx_num_channels; - shm_radio_state->rx_num_channels = openair0_cfg->rx_num_channels; + device->priv = vrtsim_state; + device->trx_write_init = vrtsim_stub; + vrtsim_state->last_received_sample = 0; + vrtsim_state->sample_rate = openair0_cfg->sample_rate; + vrtsim_state->rx_freq = openair0_cfg->rx_freq[0]; + vrtsim_state->tx_bw = openair0_cfg->tx_bw; + vrtsim_state->tx_num_channels = openair0_cfg->tx_num_channels; + vrtsim_state->rx_num_channels = openair0_cfg->rx_num_channels; - if (shm_radio_state->chanmod) { + if (vrtsim_state->chanmod) { 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)); From 347eb5a50daaf6b54dfb584f023697898fde1627 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 14 Feb 2025 11:53:10 +0100 Subject: [PATCH 7/8] Add vrtsim to build_oai and some oai images --- cmake_targets/build_oai | 2 +- docker/Dockerfile.gNB.rhel9 | 1 + docker/Dockerfile.gNB.rocky | 1 + docker/Dockerfile.gNB.ubuntu22 | 1 + docker/Dockerfile.nrUE.rhel9 | 1 + docker/Dockerfile.nrUE.rocky | 1 + docker/Dockerfile.nrUE.ubuntu22 | 1 + 7 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cmake_targets/build_oai b/cmake_targets/build_oai index 27bb63ff89..f0d08328cf 100755 --- a/cmake_targets/build_oai +++ b/cmake_targets/build_oai @@ -485,7 +485,7 @@ function main() { # add some default libraries that should always be built # for eNB, gNB, UEs, simulators if [[ $gNB == 1 || $eNB == 1 || $UE == 1 || $nrUE == 1 || $SIMUS_PHY == 1 || $RU == 1 ]]; then - TARGET_LIST="$TARGET_LIST params_libconfig coding rfsimulator dfts params_yaml" + TARGET_LIST="$TARGET_LIST params_libconfig coding rfsimulator dfts params_yaml vrtsim" fi mkdir -p $DIR/$BUILD_DIR/build diff --git a/docker/Dockerfile.gNB.rhel9 b/docker/Dockerfile.gNB.rhel9 index 894d1f6aa7..824e3b3833 100644 --- a/docker/Dockerfile.gNB.rhel9 +++ b/docker/Dockerfile.gNB.rhel9 @@ -72,6 +72,7 @@ COPY --from=gnb-build \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv.so \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv_ci.so \ /oai-ran/cmake_targets/ran_build/build/libparams_yaml.so \ + /oai-ran/cmake_targets/ran_build/build/libvrtsim.so \ /usr/local/lib/ COPY --from=gnb-base \ diff --git a/docker/Dockerfile.gNB.rocky b/docker/Dockerfile.gNB.rocky index 5c17c78e57..225432fdab 100644 --- a/docker/Dockerfile.gNB.rocky +++ b/docker/Dockerfile.gNB.rocky @@ -69,6 +69,7 @@ COPY --from=gnb-build \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv.so \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv_ci.so \ /oai-ran/cmake_targets/ran_build/build/libparams_yaml.so \ + /oai-ran/cmake_targets/ran_build/build/libvrtsim.so \ /usr/local/lib/ COPY --from=gnb-base \ diff --git a/docker/Dockerfile.gNB.ubuntu22 b/docker/Dockerfile.gNB.ubuntu22 index da2074c64b..68c903450a 100644 --- a/docker/Dockerfile.gNB.ubuntu22 +++ b/docker/Dockerfile.gNB.ubuntu22 @@ -110,6 +110,7 @@ COPY --from=gnb-build \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv_5Gue.so \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv_rrc.so \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv_o1.so \ + /oai-ran/cmake_targets/ran_build/build/libvrtsim.so \ /usr/local/lib/ # Now we are copying from builder-image the UHD files. diff --git a/docker/Dockerfile.nrUE.rhel9 b/docker/Dockerfile.nrUE.rhel9 index 8ab486bd85..3a2900da27 100644 --- a/docker/Dockerfile.nrUE.rhel9 +++ b/docker/Dockerfile.nrUE.rhel9 @@ -75,6 +75,7 @@ COPY --from=nr-ue-build \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv.so \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv_5Gue.so \ /oai-ran/cmake_targets/ran_build/build/libparams_yaml.so \ + /oai-ran/cmake_targets/ran_build/build/libvrtsim.so \ /usr/local/lib/ COPY --from=nr-ue-base \ diff --git a/docker/Dockerfile.nrUE.rocky b/docker/Dockerfile.nrUE.rocky index d0455527fa..7f112af53a 100644 --- a/docker/Dockerfile.nrUE.rocky +++ b/docker/Dockerfile.nrUE.rocky @@ -72,6 +72,7 @@ COPY --from=nr-ue-build \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv.so \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv_5Gue.so \ /oai-ran/cmake_targets/ran_build/build/libparams_yaml.so \ + /oai-ran/cmake_targets/ran_build/build/libvrtsim.so \ /usr/local/lib/ COPY --from=nr-ue-base \ diff --git a/docker/Dockerfile.nrUE.ubuntu22 b/docker/Dockerfile.nrUE.ubuntu22 index 45ad4bbe03..c3b6592753 100644 --- a/docker/Dockerfile.nrUE.ubuntu22 +++ b/docker/Dockerfile.nrUE.ubuntu22 @@ -110,6 +110,7 @@ COPY --from=nr-ue-build \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv_ciUE.so \ /oai-ran/cmake_targets/ran_build/build/libtelnetsrv_5Gue.so \ /oai-ran/cmake_targets/ran_build/build/libparams_yaml.so \ + /oai-ran/cmake_targets/ran_build/build/libvrtsim.so \ /usr/local/lib/ # Now we are copying from builder-image the UHD files. From cea02a16b14ca63fe2746657513bea9f3941e630 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 20 Feb 2025 16:23:58 +0100 Subject: [PATCH 8/8] Modify 5g_fdd_rfsimulator testcase to use shm_radio Use timescale 0.5 to make sure the iperf UL test passes. Also, disable asan as this adds computational complexity that makes the test fail. --- ci-scripts/xml_files/container_5g_fdd_rfsim.xml | 4 ++-- .../5g_fdd_rfsimulator/docker-compose.yaml | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ci-scripts/xml_files/container_5g_fdd_rfsim.xml b/ci-scripts/xml_files/container_5g_fdd_rfsim.xml index 276bb5b9a3..1c43120131 100644 --- a/ci-scripts/xml_files/container_5g_fdd_rfsim.xml +++ b/ci-scripts/xml_files/container_5g_fdd_rfsim.xml @@ -43,7 +43,7 @@ Pull_Local_Registry Pull Images from Local Registry 0 - oai-gnb-asan oai-nr-ue-asan + oai-gnb oai-nr-ue Create_Workspace @@ -134,7 +134,7 @@ true Clean Test Images on Test Server 0 - oai-gnb-asan oai-nr-ue-asan + oai-gnb oai-nr-ue diff --git a/ci-scripts/yaml_files/5g_fdd_rfsimulator/docker-compose.yaml b/ci-scripts/yaml_files/5g_fdd_rfsimulator/docker-compose.yaml index a6103a3815..8c4688262b 100644 --- a/ci-scripts/yaml_files/5g_fdd_rfsimulator/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_fdd_rfsimulator/docker-compose.yaml @@ -86,8 +86,7 @@ services: cap_drop: - ALL environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim --log_config.global_log_options level,nocolor,time - ASAN_OPTIONS: detect_leaks=0 + USE_ADDITIONAL_OPTIONS: -E --log_config.global_log_options level,nocolor,time --device.name vrtsim --vrtsim.role server --vrtsim.timescale 0.5 depends_on: - oai-ext-dn networks: @@ -95,11 +94,13 @@ services: ipv4_address: 192.168.71.140 volumes: - ../../conf_files/gnb.sa.band66.106prb.rfsim.conf:/opt/oai-gnb/etc/gnb.conf + - tmp_data:/tmp/ healthcheck: test: /bin/bash -c "pgrep nr-softmodem" interval: 10s timeout: 5s retries: 5 + ipc: host oai-nr-ue: image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} container_name: rfsim5g-oai-nr-ue @@ -109,7 +110,7 @@ services: - NET_ADMIN # for interface bringup - NET_RAW # for ping environment: - USE_ADDITIONAL_OPTIONS: -E --rfsim -r 106 --numerology 1 --uicc0.imsi 208990100001100 --band 66 -C 2169090000 --CO -400000000 --ssb 378 --rfsimulator.serveraddr 192.168.71.140 --log_config.global_log_options level,nocolor,time + USE_ADDITIONAL_OPTIONS: -E -r 106 --numerology 1 --uicc0.imsi 208990100001100 --band 66 -C 2169090000 --CO -400000000 --ssb 378 --log_config.global_log_options level,nocolor,time --device.name vrtsim depends_on: - oai-gnb networks: @@ -119,11 +120,13 @@ services: - /dev/net/tun:/dev/net/tun volumes: - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf + - tmp_data:/tmp/ healthcheck: test: /bin/bash -c "pgrep nr-uesoftmodem" interval: 10s timeout: 5s retries: 5 + ipc: host networks: public_net: @@ -142,3 +145,6 @@ networks: - subnet: 192.168.72.128/26 driver_opts: com.docker.network.bridge.name: "rfsim5g-traffic" + +volumes: + tmp_data: