Merge remote-tracking branch 'origin/vrtsim-taps-client' into integration_2025_w34 (!3407)
Taps client for vrtsim The MR allows vrtsim to connect to the Raytracing Channel Emulator (CE later). https://gitlab.eurecom.fr/oai/raytracing-channel-emulator Additional Ubuntu packages required: - libnanomsg-dev - flatbuffers-compiler - libflatbuffers-dev Everything was tested on the same host. Follow the steps below to successfully replicate: 1. Build nr-softmodem dependencies 2. Build nr-softmodem for (gNB, nrUE) with OAI_VRTSIM_TAPS_CLIENTset to ON. 3. Start the core network (Optional) follow this tutorial 4. Start the gNB sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --device.name vrtsim --vrtsim.role server --vrtsim.timescale 0.5 --vrtsim.taps-socket ipc:///tmp/ru_socket_0 5. Start the nrUE sudo ./nr-uesoftmodem -C 3619200000 -r 106 --numerology 1 --ssb 516 --band 78 --device.name vrtsim --vrtsim.taps-socket ipc:///tmp/ue_socket_0 6. Start the CE Server, once the UE and gNB are running properly, so the server can connect to IPC sockets 7. Start CE client; the binaries can be downloaded from a pipeline run or from this link 8. Move the UE in the CE client to generate a new taps array and populate the taps in softmodems
This commit is contained in:
@@ -40,7 +40,6 @@ typedef struct {
|
||||
int num_antennas_tx;
|
||||
int num_antennas_rx;
|
||||
uint64_t timestamp;
|
||||
bool is_connected;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
} ShmTDIQChannelData;
|
||||
@@ -48,10 +47,10 @@ typedef struct {
|
||||
typedef struct ShmTDIQChannel_s {
|
||||
IQChannelType type;
|
||||
ShmTDIQChannelData *data;
|
||||
uint64_t last_timestamp;
|
||||
char name[256];
|
||||
sample_t *tx_iq_data;
|
||||
sample_t *rx_iq_data;
|
||||
bool abort;
|
||||
} ShmTDIQChannel;
|
||||
|
||||
ShmTDIQChannel *shm_td_iq_channel_create(const char *name, int num_tx_ant, int num_rx_ant)
|
||||
@@ -75,14 +74,12 @@ ShmTDIQChannel *shm_td_iq_channel_create(const char *name, int num_tx_ant, int n
|
||||
memset(shm_ptr, 0, total_size);
|
||||
shm_ptr->num_antennas_tx = num_tx_ant;
|
||||
shm_ptr->num_antennas_rx = num_rx_ant;
|
||||
shm_ptr->is_connected = false;
|
||||
ShmTDIQChannel *channel = calloc_or_fail(1, sizeof(ShmTDIQChannel));
|
||||
strncpy(channel->name, name, sizeof(channel->name) - 1);
|
||||
channel->tx_iq_data = (sample_t *)(shm_ptr + 1);
|
||||
channel->rx_iq_data = channel->tx_iq_data + tx_buffer_size / sizeof(sample_t);
|
||||
channel->data = shm_ptr;
|
||||
channel->type = IQ_CHANNEL_TYPE_SERVER;
|
||||
channel->last_timestamp = 0;
|
||||
pthread_mutexattr_t mutex_attr;
|
||||
pthread_condattr_t cond_attr;
|
||||
int ret = pthread_mutexattr_init(&mutex_attr);
|
||||
@@ -137,12 +134,10 @@ ShmTDIQChannel *shm_td_iq_channel_connect(const char *name, int timeout_in_secon
|
||||
size_t tx_buffer_size = CIRCULAR_BUFFER_SIZE * sizeof(sample_t) * channel->data->num_antennas_tx;
|
||||
channel->rx_iq_data = channel->tx_iq_data + tx_buffer_size / sizeof(sample_t);
|
||||
channel->type = IQ_CHANNEL_TYPE_CLIENT;
|
||||
channel->last_timestamp = 0;
|
||||
while (shm_ptr->magic != SHM_MAGIC_NUMBER) {
|
||||
printf("Waiting for server to initialize shared memory\n");
|
||||
sleep(1);
|
||||
}
|
||||
shm_ptr->is_connected = true;
|
||||
close(fd);
|
||||
return channel;
|
||||
}
|
||||
@@ -154,9 +149,6 @@ IQChannelErrorType shm_td_iq_channel_tx(ShmTDIQChannel *channel,
|
||||
const sample_t *tx_iq_data)
|
||||
{
|
||||
ShmTDIQChannelData *data = channel->data;
|
||||
if (data->is_connected == false) {
|
||||
return CHANNEL_ERROR_NOT_CONNECTED;
|
||||
}
|
||||
// timestamp in the past
|
||||
uint64_t current_time = data->timestamp;
|
||||
if (timestamp < current_time) {
|
||||
@@ -193,9 +185,6 @@ IQChannelErrorType shm_td_iq_channel_rx(ShmTDIQChannel *channel,
|
||||
sample_t *tx_iq_data)
|
||||
{
|
||||
ShmTDIQChannelData *data = channel->data;
|
||||
if (data->is_connected == false) {
|
||||
return CHANNEL_ERROR_NOT_CONNECTED;
|
||||
}
|
||||
// timestamp in the future
|
||||
uint64_t current_time = data->timestamp;
|
||||
if (timestamp > current_time) {
|
||||
@@ -228,37 +217,54 @@ IQChannelErrorType shm_td_iq_channel_rx(ShmTDIQChannel *channel,
|
||||
void shm_td_iq_channel_produce_samples(ShmTDIQChannel *channel, size_t num_samples)
|
||||
{
|
||||
ShmTDIQChannelData *data = channel->data;
|
||||
if (channel->type != IQ_CHANNEL_TYPE_SERVER) {
|
||||
return;
|
||||
}
|
||||
if (data->is_connected == false) {
|
||||
return;
|
||||
}
|
||||
mutexlock(data->mutex);
|
||||
data->timestamp += num_samples;
|
||||
condbroadcast(data->cond);
|
||||
mutexunlock(data->mutex);
|
||||
}
|
||||
|
||||
void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp)
|
||||
int shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_t timeout_uS)
|
||||
{
|
||||
ShmTDIQChannelData *data = channel->data;
|
||||
if (data->is_connected == false) {
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
|
||||
size_t current_timestamp = data->timestamp;
|
||||
if (current_timestamp >= timestamp) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
mutexlock(data->mutex);
|
||||
while (current_timestamp < timestamp) {
|
||||
condwait(data->cond, data->mutex);
|
||||
current_timestamp = data->timestamp;
|
||||
if (timeout_uS == 0) {
|
||||
mutexlock(data->mutex);
|
||||
while (current_timestamp < timestamp && !channel->abort) {
|
||||
condwait(data->cond, data->mutex);
|
||||
current_timestamp = data->timestamp;
|
||||
}
|
||||
mutexunlock(data->mutex);
|
||||
} else {
|
||||
struct timespec ts = {.tv_sec = 0, .tv_nsec = 0};
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
|
||||
fprintf(stderr, "Error: clock_gettime failed: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
ts.tv_sec += timeout_uS / 1000000; // Convert microseconds to seconds
|
||||
ts.tv_nsec += (timeout_uS % 1000000) * 1000; // Convert remaining microseconds to nanoseconds
|
||||
|
||||
mutexlock(data->mutex);
|
||||
while (current_timestamp < timestamp && !channel->abort) {
|
||||
int ret = pthread_cond_timedwait(&data->cond, &data->mutex, &ts);
|
||||
if (ret == ETIMEDOUT) {
|
||||
fprintf(stderr, "Error: Timed out waiting for samples.\n");
|
||||
mutexunlock(data->mutex);
|
||||
return 1;
|
||||
} else if (ret != 0) {
|
||||
fprintf(stderr, "Error: pthread_cond_timedwait failed: %s\n", strerror(ret));
|
||||
mutexunlock(data->mutex);
|
||||
return 1;
|
||||
} else {
|
||||
current_timestamp = data->timestamp;
|
||||
}
|
||||
}
|
||||
mutexunlock(data->mutex);
|
||||
}
|
||||
mutexunlock(data->mutex);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t shm_td_iq_channel_get_current_sample(const ShmTDIQChannel *channel)
|
||||
@@ -267,9 +273,18 @@ uint64_t shm_td_iq_channel_get_current_sample(const ShmTDIQChannel *channel)
|
||||
return data->timestamp;
|
||||
}
|
||||
|
||||
bool shm_td_iq_channel_is_connected(const ShmTDIQChannel *channel)
|
||||
void shm_td_iq_channel_abort(ShmTDIQChannel *channel)
|
||||
{
|
||||
return channel->data->is_connected;
|
||||
ShmTDIQChannelData *data = channel->data;
|
||||
mutexlock(data->mutex);
|
||||
channel->abort = true;
|
||||
condbroadcast(data->cond);
|
||||
mutexunlock(data->mutex);
|
||||
}
|
||||
|
||||
bool shm_td_iq_channel_is_aborted(const ShmTDIQChannel *channel)
|
||||
{
|
||||
return channel->abort;
|
||||
}
|
||||
|
||||
void shm_td_iq_channel_destroy(ShmTDIQChannel *channel)
|
||||
@@ -278,9 +293,11 @@ void shm_td_iq_channel_destroy(ShmTDIQChannel *channel)
|
||||
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) {
|
||||
munmap(data, total_size);
|
||||
shm_unlink(channel->name);
|
||||
} else {
|
||||
munmap(data, total_size);
|
||||
}
|
||||
free(channel);
|
||||
}
|
||||
|
||||
@@ -118,16 +118,27 @@ void shm_td_iq_channel_produce_samples(ShmTDIQChannel *channel, uint64_t num_sam
|
||||
* @brief Wait until sample at the specified timestamp is available
|
||||
*
|
||||
* @param channel The ShmTDIQChannel structure.
|
||||
* @param timestamp The timestamp for which to wait.
|
||||
* @param timeout_uS The timeout in microseconds to wait for the sample. 0 means wait indefinitely.
|
||||
*
|
||||
* @return 0 if the sample is available, 1 if timed out
|
||||
*/
|
||||
void shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp);
|
||||
int shm_td_iq_channel_wait(ShmTDIQChannel *channel, uint64_t timestamp, uint64_t timeout_uS);
|
||||
|
||||
/**
|
||||
* @brief Checks if the IQ channel is connected.
|
||||
* @brief Aborts the IQ channel causing the wait to return immediately
|
||||
*
|
||||
* @param channel The ShmTDIQChannel structure.
|
||||
* @return True if the channel is connected, false otherwise.
|
||||
*/
|
||||
bool shm_td_iq_channel_is_connected(const ShmTDIQChannel *channel);
|
||||
void shm_td_iq_channel_abort(ShmTDIQChannel *channel);
|
||||
|
||||
/**
|
||||
* @brief Checks if the IQ channel is aborted.
|
||||
*
|
||||
* @param channel The ShmTDIQChannel structure.
|
||||
* @return True if the channel is aborted, false otherwise.
|
||||
*/
|
||||
bool shm_td_iq_channel_is_aborted(const ShmTDIQChannel *channel);
|
||||
|
||||
/**
|
||||
* @brief Destroys the shared memory IQ channel.
|
||||
|
||||
@@ -87,15 +87,6 @@ void server(void)
|
||||
int num_ant_tx = 1;
|
||||
int num_ant_rx = 1;
|
||||
ShmTDIQChannel *channel = shm_td_iq_channel_create(SHM_CHANNEL_NAME, num_ant_tx, num_ant_rx);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (shm_td_iq_channel_is_connected(channel)) {
|
||||
printf("Server connected\n");
|
||||
break;
|
||||
}
|
||||
printf("Waiting for client\n");
|
||||
sleep(1);
|
||||
}
|
||||
AssertFatal(shm_td_iq_channel_is_connected(channel), "Server failed to connect\n");
|
||||
|
||||
pthread_t producer_thread;
|
||||
int ret = pthread_create(&producer_thread, NULL, produce_symbols, channel);
|
||||
@@ -104,7 +95,7 @@ void server(void)
|
||||
uint64_t timestamp = 0;
|
||||
int iq_contents = 0;
|
||||
while (timestamp < num_samples_per_update * num_updates) {
|
||||
shm_td_iq_channel_wait(channel, timestamp + num_samples_per_update);
|
||||
shm_td_iq_channel_wait(channel, timestamp + num_samples_per_update, 0);
|
||||
uint64_t target_timestamp = timestamp + num_samples_per_update;
|
||||
timestamp += num_samples_per_update;
|
||||
uint32_t iq_data[num_samples_per_update];
|
||||
@@ -127,21 +118,12 @@ 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);
|
||||
shm_td_iq_channel_wait(channel, timestamp + num_samples_per_update, 0);
|
||||
// Server starts producing from second slot
|
||||
if (timestamp > num_samples_per_update) {
|
||||
uint64_t target_timestamp = timestamp - num_samples_per_update;
|
||||
|
||||
@@ -77,6 +77,8 @@ typedef struct {
|
||||
struct complexd **a;
|
||||
///interpolated (sample-spaced) channel impulse response. size(ch) = (n_tx * n_rx) * channel_length. ATTENTION: the dimensions of ch are the transposed ones of a. This is to allow the use of BLAS when applying the correlation matrices to the state.
|
||||
struct complexd **ch;
|
||||
///Same as above but single precision
|
||||
struct complexf **ch_ps;
|
||||
///Sampled frequency response (90 kHz resolution)
|
||||
struct complexd **chF;
|
||||
///Maximum path delay in mus.
|
||||
|
||||
@@ -38,7 +38,7 @@ if(OAI_USRP)
|
||||
add_subdirectory(USRP)
|
||||
endif()
|
||||
|
||||
add_boolean_option(VRTSIM_RADIO ON "Activate OAI's shared memory radio driver" OFF)
|
||||
if(VRTSIM_RADIO)
|
||||
add_boolean_option(OAI_VRTSIM ON "Activate OAI's shared memory radio driver" OFF)
|
||||
if(OAI_VRTSIM)
|
||||
add_subdirectory(vrtsim)
|
||||
endif()
|
||||
|
||||
@@ -2,3 +2,28 @@ add_library(vrtsim MODULE vrtsim.c noise_device.c)
|
||||
set_target_properties(vrtsim PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
target_link_libraries(vrtsim PRIVATE SIMU shm_td_iq_channel actor)
|
||||
add_dependencies(vrtsim generate_T)
|
||||
|
||||
add_boolean_option(OAI_VRTSIM_TAPS_CLIENT OFF "Enable taps client" ON)
|
||||
if (OAI_VRTSIM_TAPS_CLIENT)
|
||||
target_link_libraries(vrtsim PRIVATE taps_client)
|
||||
file(DOWNLOAD
|
||||
"https://gitlab.eurecom.fr/oai/raytracing-channel-emulator/-/raw/59589ad2a534881810b2aa7dd81e63066245bb8d/server/api/taps.fbs?inline=false"
|
||||
${CMAKE_SOURCE_DIR}/taps.fbs
|
||||
)
|
||||
|
||||
set(TAPS_IF_PATH ${CMAKE_SOURCE_DIR}/taps.fbs)
|
||||
set(TAPS_API_HEADER taps_generated.h)
|
||||
|
||||
add_custom_command(OUTPUT ${TAPS_API_HEADER}
|
||||
COMMAND flatc -c ${TAPS_IF_PATH}
|
||||
DEPENDS ${TAPS_IF_PATH}
|
||||
COMMENT "Generating flatbuffers API from ${TAPS_IF_PATH}"
|
||||
)
|
||||
add_library(taps_api INTERFACE)
|
||||
target_include_directories(taps_api INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
add_library(taps_client taps_client.cpp ${TAPS_API_HEADER})
|
||||
target_include_directories(taps_client PUBLIC .)
|
||||
find_package(Flatbuffers REQUIRED)
|
||||
target_link_libraries(taps_client PUBLIC flatbuffers taps_api SIMU nanomsg)
|
||||
endif()
|
||||
|
||||
+75
-5
@@ -4,12 +4,24 @@ This implements a shared memory realtime and near-realtime radio interface.
|
||||
This is performed using `shm_td_iq_channel` which handles the shared memory
|
||||
interface.
|
||||
|
||||
# Architecture
|
||||
|
||||
The `vrtsim` architecture follows a server-client model:
|
||||
|
||||
- **Server (gNB)**: On startup, the server writes a file, default location is
|
||||
`/tmp/vrtsim_connection`. This file contains information required by the client
|
||||
to establish communication.
|
||||
- **Client (UE)**: The client reads this file from, verifies its contents, and uses
|
||||
the provided information to open and connect to the shared memory channel.
|
||||
- **Shared Memory Channel**: Both server and client use the `shm_td_iq_channel` to
|
||||
exchange IQ samples and control data in real time or near-real time.
|
||||
|
||||
## Channel modelling:
|
||||
|
||||
|
||||
# Limitations
|
||||
|
||||
- Only 1gNB to 1nrUE: gNB as a server, UE as a client.
|
||||
- Server only accepts connections during device bringup. This means that
|
||||
restarting the client will not make it reconnect, the server has to be
|
||||
restarted as well.
|
||||
|
||||
# Usage
|
||||
|
||||
@@ -21,5 +33,63 @@ is the default and means realtime.
|
||||
|
||||
Channel modelling can be enabled by adding `vrtsim.chanmod 1` to the
|
||||
command line and should work the same as channel modelling in rfsimulator,
|
||||
see rfsimulator [documentation](../rfsimulator/README.md), provided that your
|
||||
CPU is fast enough.
|
||||
see rfsimulator [documentation](../rfsimulator/README.md) with a slight difference:
|
||||
channel modelling is done on transmission instead of reception. To refect this,
|
||||
the model names are slightly different: the server expects a model named
|
||||
`server_tx_channel_model` while the client expects a model named `client_tx_channel_model`
|
||||
|
||||
Additionally, `taps_client` is available in vrtsim. This allows to connect to
|
||||
channel emulation server and receive external taps (or channel impulse responses).
|
||||
To use external taps, use `--taps-socket` and provide a nanomsg PUB socket address
|
||||
that will publish the taps.
|
||||
|
||||
# Debugging
|
||||
|
||||
## Realtime issues
|
||||
|
||||
Realtime issues can prevent the UE from connecting to the gNB, especially with
|
||||
channel modelling enabled. Consider adjusting `timescale` parameter until
|
||||
the UE can connect reliably.
|
||||
|
||||
On exit, vrtsim will output a histogram of the measured transmission budget, which
|
||||
is defined as the number of microseconds before the transmitted samples could be
|
||||
read by peer.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
[HW] VRTSIM: Average TX budget 939.210 uS (more is better)
|
||||
[HW] VRTSIM: TX budget histogram: 216 samples
|
||||
[HW] Bin 0 [0.0 - 100.0uS]: 0
|
||||
[HW] Bin 1 [100.0 - 200.0uS]: 0
|
||||
[HW] Bin 2 [200.0 - 300.0uS]: 0
|
||||
[HW] Bin 3 [300.0 - 400.0uS]: 0
|
||||
[HW] Bin 4 [400.0 - 500.0uS]: 0
|
||||
[HW] Bin 5 [500.0 - 600.0uS]: 0
|
||||
[HW] Bin 6 [600.0 - 700.0uS]: 0
|
||||
[HW] Bin 7 [700.0 - 800.0uS]: 0
|
||||
[HW] Bin 8 [800.0 - 900.0uS]: 0
|
||||
[HW] Bin 9 [900.0 - 1000.0uS]: 117
|
||||
[HW] Bin 10 [1000.0 - 1100.0uS]: 0
|
||||
[HW] Bin 11 [1100.0 - 1200.0uS]: 0
|
||||
[HW] Bin 12 [1200.0 - 1300.0uS]: 0
|
||||
[HW] Bin 13 [1300.0 - 1400.0uS]: 0
|
||||
[HW] Bin 14 [1400.0 - 1500.0uS]: 0
|
||||
[HW] Bin 15 [1500.0 - 1600.0uS]: 0
|
||||
[HW] Bin 16 [1600.0 - 1700.0uS]: 0
|
||||
[HW] Bin 17 [1700.0 - 1800.0uS]: 0
|
||||
[HW] Bin 18 [1800.0 - 1900.0uS]: 0
|
||||
[HW] Bin 19 [1900.0 - 2000.0uS]: 0
|
||||
[HW] Bin 20 [2000.0 - 2100.0uS]: 0
|
||||
[HW] Bin 21 [2100.0 - 2200.0uS]: 0
|
||||
[HW] Bin 22 [2200.0 - 2300.0uS]: 0
|
||||
[HW] Bin 23 [2300.0 - 2400.0uS]: 0
|
||||
[HW] Bin 24 [2400.0 - 2500.0uS]: 0
|
||||
[HW] Bin 25 [2500.0 - 2600.0uS]: 0
|
||||
[HW] Bin 26 [2600.0 - 2700.0uS]: 0
|
||||
[HW] Bin 27 [2700.0 - 2800.0uS]: 0
|
||||
[HW] Bin 28 [2800.0 - 2900.0uS]: 0
|
||||
[HW] Bin 29 [2900.0 - 3000.0uS]: 0
|
||||
```
|
||||
|
||||
Samples in bin 0 indicate realtime issues.
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
* For more information about the OpenAirInterface (OAI) Software Alliance:
|
||||
* contact@openairinterface.org
|
||||
*/
|
||||
#include "assertions.h"
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include "noise_device.h"
|
||||
@@ -74,7 +75,8 @@ void init_noise_device(float noise_power)
|
||||
void free_noise_device(void)
|
||||
{
|
||||
state.running = false;
|
||||
pthread_join(state.noise_device_thread, NULL);
|
||||
int ret = pthread_join(state.noise_device_thread, NULL);
|
||||
AssertFatal(ret == 0, "pthread_join failed: %d, errno %d (%s)\n", ret, errno, strerror(errno));
|
||||
}
|
||||
|
||||
void get_noise_vector(float *noise_vector, int length)
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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 <nanomsg/nn.h>
|
||||
#include <nanomsg/pubsub.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "pthread.h"
|
||||
#include "taps_generated.h"
|
||||
#include "SIMULATION/TOOLS/sim.h"
|
||||
extern "C" {
|
||||
#include "assertions.h"
|
||||
#include "common/utils/LOG/log.h"
|
||||
#include "sim.h"
|
||||
#include "utils.h"
|
||||
}
|
||||
#include <cfloat>
|
||||
|
||||
#define NUM_TAPS_BUFFERS 4
|
||||
#define MAX_TAPS_LEN 100
|
||||
#define MAX_TAPS_MSG_SIZE (sizeof(struct complexf) * MAX_TAPS_LEN * 4 * 4 + 20)
|
||||
|
||||
static pthread_t client_thread;
|
||||
static bool should_run = true;
|
||||
typedef struct {
|
||||
int id;
|
||||
int sock;
|
||||
uint32_t num_tx_antennas;
|
||||
uint32_t num_rx_antennas;
|
||||
channel_desc_t **channel_desc;
|
||||
} client_thread_args_t;
|
||||
|
||||
typedef struct {
|
||||
void *taps_msg;
|
||||
channel_desc_t *channel_desc;
|
||||
} taps_buffer_t;
|
||||
|
||||
typedef struct {
|
||||
taps_buffer_t taps_buffers[NUM_TAPS_BUFFERS];
|
||||
int current_buffer;
|
||||
} taps_storage_t;
|
||||
|
||||
void ascii_line_plot(const float *data, size_t size, char *buffer)
|
||||
{
|
||||
const char levels[] = "_.-=#"; // ASCII characters for different levels
|
||||
size_t num_levels = sizeof(levels) - 1; // Number of levels (excluding the null terminator)
|
||||
|
||||
// Calculate min and max values from the data
|
||||
float min_val = FLT_MAX;
|
||||
float max_val = FLT_MIN;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
if (data[i] < min_val)
|
||||
min_val = data[i];
|
||||
if (data[i] > max_val)
|
||||
max_val = data[i];
|
||||
}
|
||||
|
||||
// Handle edge case where all values are the same
|
||||
if (min_val == max_val) {
|
||||
min_val -= 1.0f;
|
||||
max_val += 1.0f;
|
||||
}
|
||||
|
||||
// Normalize and map data to levels
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
float normalized = (data[i] - min_val) / (max_val - min_val); // Normalize to [0, 1]
|
||||
size_t level_index = (size_t)(normalized * num_levels); // Map to level index
|
||||
if (level_index > num_levels)
|
||||
level_index = num_levels; // Clamp to valid range
|
||||
snprintf(buffer + i, 2, "%c", levels[level_index]);
|
||||
}
|
||||
}
|
||||
|
||||
static void init_taps_storage(taps_storage_t *storage, int num_tx_antennas, int num_rx_antennas)
|
||||
{
|
||||
for (int i = 0; i < NUM_TAPS_BUFFERS; i++) {
|
||||
storage->taps_buffers[i].taps_msg = calloc_or_fail(1, MAX_TAPS_MSG_SIZE);
|
||||
storage->taps_buffers[i].channel_desc = (channel_desc_t *)calloc_or_fail(1, sizeof(channel_desc_t));
|
||||
storage->taps_buffers[i].channel_desc->ch_ps =
|
||||
(struct complexf **)calloc_or_fail(num_rx_antennas * num_tx_antennas, sizeof(struct complexf *));
|
||||
}
|
||||
storage->current_buffer = 0;
|
||||
}
|
||||
|
||||
static taps_storage_t taps_storage;
|
||||
|
||||
void *client_thread_func(void *args)
|
||||
{
|
||||
client_thread_args_t *client_thread_args = (client_thread_args_t *)args;
|
||||
while (should_run) {
|
||||
int next_buffer = (taps_storage.current_buffer + 1) % NUM_TAPS_BUFFERS;
|
||||
taps_buffer_t *taps_buffer = &taps_storage.taps_buffers[next_buffer];
|
||||
int ret = nn_recv(client_thread_args->sock, taps_buffer->taps_msg, MAX_TAPS_MSG_SIZE, NN_DONTWAIT);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
// Timeout: no message available, sleep briefly and continue
|
||||
usleep(100);
|
||||
continue;
|
||||
}
|
||||
LOG_E(HW, "nn_recv() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
auto taps_message = Phy::GetTaps(taps_buffer->taps_msg);
|
||||
if (taps_message->num_rx_antennas() != client_thread_args->num_rx_antennas
|
||||
|| taps_message->num_tx_antennas() != client_thread_args->num_tx_antennas) {
|
||||
LOG_E(HW,
|
||||
"Number of antennas mismatch: expected %d x %d, got %d x %d\n",
|
||||
client_thread_args->num_rx_antennas,
|
||||
client_thread_args->num_tx_antennas,
|
||||
taps_message->num_rx_antennas(),
|
||||
taps_message->num_tx_antennas());
|
||||
continue;
|
||||
}
|
||||
channel_desc_t *channel_desc = taps_buffer->channel_desc;
|
||||
const flatbuffers::Vector<float> *taps = taps_message->taps();
|
||||
struct complexf *base_pointer = (struct complexf *)taps->data();
|
||||
int taps_len = taps_message->taps_len();
|
||||
AssertFatal(taps_len < MAX_TAPS_LEN, "Too many samples in the taps array, taps_len = %d\n", taps_len);
|
||||
for (unsigned int aarx = 0U; aarx < client_thread_args->num_rx_antennas; aarx++) {
|
||||
for (unsigned int aatx = 0U; aatx < client_thread_args->num_tx_antennas; aatx++) {
|
||||
channel_desc->ch_ps[aarx + (client_thread_args->num_rx_antennas * aatx)] =
|
||||
&base_pointer[(aarx + (client_thread_args->num_rx_antennas * aatx)) * taps_len];
|
||||
}
|
||||
}
|
||||
channel_desc->path_loss_dB = 0;
|
||||
channel_desc->channel_length = taps_len;
|
||||
*client_thread_args->channel_desc = channel_desc;
|
||||
taps_storage.current_buffer = next_buffer;
|
||||
LOG_A(HW, "Receved new taps message, channel_length %d, buffer %d\n", channel_desc->channel_length, next_buffer);
|
||||
for (unsigned int aarx = 0; aarx < client_thread_args->num_rx_antennas; aarx++) {
|
||||
for (unsigned int aatx = 0; aatx < client_thread_args->num_tx_antennas; aatx++) {
|
||||
char buffer[MAX_TAPS_LEN + 1];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
float magnitudes[MAX_TAPS_LEN];
|
||||
cf_t *channel = channel_desc->ch_ps[aarx + (client_thread_args->num_rx_antennas * aatx)];
|
||||
for (int i = 0; i < channel_desc->channel_length; i++) {
|
||||
magnitudes[i] = sqrtf(powf(channel[i].r, 2) + powf(channel[i].i, 2));
|
||||
}
|
||||
ascii_line_plot(magnitudes, channel_desc->channel_length, buffer);
|
||||
LOG_A(HW, "Taps message %d, channel %d x %d: %s\n", client_thread_args->id, aarx, aatx, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern "C" void taps_client_connect(int id,
|
||||
const char *socket_path,
|
||||
int num_tx_antennas,
|
||||
int num_rx_antennas,
|
||||
channel_desc_t **channel_desc)
|
||||
{
|
||||
// Create a socket
|
||||
int sock = nn_socket(AF_SP, NN_SUB);
|
||||
AssertFatal(sock >= 0, "nn_socket() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
int ret = nn_connect(sock, socket_path);
|
||||
AssertFatal(ret >= 0, "nn_connect() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
// Subscribe to all messages
|
||||
ret = nn_setsockopt(sock, NN_SUB, NN_SUB_SUBSCRIBE, "", 0);
|
||||
AssertFatal(ret == 0, "nn_setsockopt() failed, errno %d, %s\n", errno, strerror(errno));
|
||||
|
||||
init_taps_storage(&taps_storage, num_tx_antennas, num_rx_antennas);
|
||||
|
||||
client_thread_args_t *client_thread_args = static_cast<client_thread_args_t *>(malloc(sizeof(client_thread_args_t)));
|
||||
client_thread_args->id = id;
|
||||
client_thread_args->sock = sock;
|
||||
client_thread_args->num_rx_antennas = num_rx_antennas;
|
||||
client_thread_args->num_tx_antennas = num_tx_antennas;
|
||||
client_thread_args->channel_desc = channel_desc;
|
||||
ret = pthread_create(&client_thread, NULL, client_thread_func, client_thread_args);
|
||||
AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
}
|
||||
|
||||
extern "C" void taps_client_stop()
|
||||
{
|
||||
should_run = false;
|
||||
pthread_join(client_thread, NULL);
|
||||
for (int i = 0; i < NUM_TAPS_BUFFERS; i++) {
|
||||
free(taps_storage.taps_buffers[i].taps_msg);
|
||||
free(taps_storage.taps_buffers[i].channel_desc->ch_ps);
|
||||
free(taps_storage.taps_buffers[i].channel_desc);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The OpenAirInterface Software Alliance licenses this file to You under
|
||||
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.openairinterface.org/?page_id=698
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*-------------------------------------------------------------------------------
|
||||
* For more information about the OpenAirInterface (OAI) Software Alliance:
|
||||
* contact@openairinterface.org
|
||||
*/
|
||||
|
||||
#ifndef TAPS_CLIENT_H
|
||||
#define TAPS_CLIENT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "sim.h"
|
||||
|
||||
void taps_client_connect(int id, const char *socket_path, int num_tx_antennas, int num_rx_antennas, channel_desc_t **channel_desc);
|
||||
void taps_client_stop();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+208
-140
@@ -19,6 +19,7 @@
|
||||
* contact@openairinterface.org
|
||||
*/
|
||||
|
||||
#include "PHY/TOOLS/tools_defs.h"
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
@@ -34,6 +35,7 @@
|
||||
#include <pthread.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/limits.h>
|
||||
|
||||
#include <common/utils/assertions.h>
|
||||
#include <common/utils/LOG/log.h>
|
||||
@@ -46,11 +48,13 @@
|
||||
#include "actor.h"
|
||||
#include "noise_device.h"
|
||||
#include "simde/x86/avx512.h"
|
||||
#include "taps_client.h"
|
||||
|
||||
// Simulator role
|
||||
typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
|
||||
|
||||
#define NUM_CHANMOD_THREADS 1
|
||||
#define MAX_NUM_ANTENNAS_TX 4
|
||||
#define MAX_CHANNEL_LENGTH (1 << 20)
|
||||
|
||||
#define ROLE_CLIENT_STRING "client"
|
||||
#define ROLE_SERVER_STRING "server"
|
||||
@@ -58,14 +62,21 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
|
||||
#define VRTSIM_SECTION "vrtsim"
|
||||
#define TIME_SCALE_HLP \
|
||||
"sample time scale. 1.0 means realtime. Values > 1 mean faster than realtime. Values < 1 mean slower than realtime\n"
|
||||
#define TAPS_SOCKET_HLP "Socket to connect to the channel emulation server\n"
|
||||
#define CLIENT_NUM_RX_HLP "Number of RX antennas of the client, specified on the server\n"
|
||||
#define CONNECTION_DESCRIPTOR_HLP "Path to the file written by the server that the client can use to connect."
|
||||
#define DEFAULT_CHANNEL_NAME "vrtsim_channel"
|
||||
#define DEFAULT_DESCRIPTOR "/tmp/vrtsim_connection"
|
||||
|
||||
// clang-format off
|
||||
#define VRTSIM_PARAMS_DESC \
|
||||
{ \
|
||||
{"channel_name", "shared memory channel name\n", 0, .strptr = &channel_name, .defstrval = "vrtsim_channel", TYPE_STRING, 0}, \
|
||||
{"role", "either client or server\n", 0, .strptr = &role, .defstrval = ROLE_CLIENT_STRING, TYPE_STRING, 0}, \
|
||||
{"timescale", TIME_SCALE_HLP, 0, .dblptr = &vrtsim_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \
|
||||
{"chanmod", "Enable channel modelling", 0, .iptr = &vrtsim_state->chanmod, .defintval = 0, TYPE_INT, 0}, \
|
||||
{"connection_descriptor", CONNECTION_DESCRIPTOR_HLP, 0, .strptr = &vrtsim_state->connection_descriptor, .defstrval = DEFAULT_DESCRIPTOR, TYPE_STRING, 0}, \
|
||||
{"role", "either client or server\n", 0, .strptr = &role, .defstrval = ROLE_CLIENT_STRING, TYPE_STRING, 0}, \
|
||||
{"timescale", TIME_SCALE_HLP, 0, .dblptr = &vrtsim_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \
|
||||
{"chanmod", "Enable channel modelling", 0, .iptr = &vrtsim_state->chanmod, .defintval = 0, TYPE_INT, 0}, \
|
||||
{"taps-socket", TAPS_SOCKET_HLP, 0, .strptr = &vrtsim_state->taps_socket, .defstrval = NULL, TYPE_STRING, 0}, \
|
||||
{"client-num-rx-antennas", CLIENT_NUM_RX_HLP, 0, .iptr = &vrtsim_state->client_num_rx_antennas, .defintval = 1, TYPE_INT, 0}, \
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -91,7 +102,7 @@ typedef struct tx_timing_s {
|
||||
|
||||
typedef struct {
|
||||
int role;
|
||||
char channel_name[40];
|
||||
char *connection_descriptor;
|
||||
ShmTDIQChannel *channel;
|
||||
uint64_t last_received_sample;
|
||||
pthread_t timing_thread;
|
||||
@@ -110,8 +121,13 @@ typedef struct {
|
||||
int rx_num_channels;
|
||||
channel_desc_t *channel_desc;
|
||||
Actor_t *channel_modelling_actors;
|
||||
char *taps_socket;
|
||||
int client_num_rx_antennas;
|
||||
} vrtsim_state_t;
|
||||
|
||||
// Sample history for channel impulse response
|
||||
static c16_t saved_samples[MAX_NUM_ANTENNAS_TX][MAX_CHANNEL_LENGTH] __attribute__((aligned(32))) = {0};
|
||||
|
||||
static void histogram_add(histogram_t *histogram, double diff)
|
||||
{
|
||||
histogram->num_samples++;
|
||||
@@ -149,18 +165,26 @@ static void load_channel_model(vrtsim_state_t *vrtsim_state)
|
||||
vrtsim_state->tx_bw);
|
||||
char *model_name = vrtsim_state->role == ROLE_CLIENT ? "client_tx_channel_model" : "server_tx_channel_model";
|
||||
vrtsim_state->channel_desc = find_channel_desc_fromname(model_name);
|
||||
AssertFatal(vrtsim_state->channel_desc != NULL,
|
||||
"Could not find model name %s. Make sure it is present in the config file",
|
||||
model_name);
|
||||
LOG_I(HW,
|
||||
"Channel model %s parameters: path_loss_dB=%.2f, nb_tx=%d, nb_rx=%d, channel_length=%d\n",
|
||||
model_name,
|
||||
vrtsim_state->channel_desc->path_loss_dB,
|
||||
vrtsim_state->channel_desc->nb_tx,
|
||||
vrtsim_state->channel_desc->nb_rx,
|
||||
vrtsim_state->channel_desc->channel_length);
|
||||
random_channel(vrtsim_state->channel_desc, 0);
|
||||
AssertFatal(vrtsim_state->channel_desc != NULL, "Could not find channel model %s\n", model_name);
|
||||
}
|
||||
|
||||
static void vrtsim_readconfig(vrtsim_state_t *vrtsim_state)
|
||||
{
|
||||
char *channel_name = NULL;
|
||||
char *role = NULL;
|
||||
paramdef_t vrtsim_params[] = VRTSIM_PARAMS_DESC;
|
||||
int ret = config_get(config_get_if(), vrtsim_params, sizeofArray(vrtsim_params), VRTSIM_SECTION);
|
||||
AssertFatal(ret >= 0, "configuration couldn't be performed\n");
|
||||
strncpy(vrtsim_state->channel_name, channel_name, sizeof(vrtsim_state->channel_name) - 1);
|
||||
if (strncmp(role, ROLE_CLIENT_STRING, strlen(ROLE_CLIENT_STRING)) == 0) {
|
||||
vrtsim_state->role = ROLE_CLIENT;
|
||||
} else if (strncmp(role, ROLE_SERVER_STRING, strlen(ROLE_SERVER_STRING)) == 0) {
|
||||
@@ -168,6 +192,15 @@ static void vrtsim_readconfig(vrtsim_state_t *vrtsim_state)
|
||||
} else {
|
||||
AssertFatal(false, "Invalid role configuration\n");
|
||||
}
|
||||
#ifdef ENABLE_TAPS_CLIENT
|
||||
if (vrtsim_state->taps_socket) {
|
||||
LOG_A(HW, "VRTSIM: will use taps socket %s\n", vrtsim_state->taps_socket);
|
||||
}
|
||||
#else
|
||||
if (vrtsim_state->taps_socket) {
|
||||
AssertFatal(false, "Invalid configuration: Build with ENABLE_TAPS_CLIENT to use taps socket\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void *vrtsim_timing_job(void *arg)
|
||||
@@ -195,128 +228,105 @@ static void *vrtsim_timing_job(void *arg)
|
||||
samples_to_produce += 1;
|
||||
leftover_samples -= 1;
|
||||
}
|
||||
AssertFatal(samples_to_produce >= 0, "Negative samples to produce: %f\n", samples_to_produce);
|
||||
shm_td_iq_channel_produce_samples(vrtsim_state->channel, samples_to_produce);
|
||||
usleep(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct client_info_s {
|
||||
int server_num_rx_antennas;
|
||||
int client_num_rx_antennas;
|
||||
} client_info_t;
|
||||
|
||||
/**
|
||||
* @brief Exchanges peer information between the server and the client.
|
||||
* @brief Publishes the client information information to a file for the client to read.
|
||||
*
|
||||
* This function sets up a Unix domain socket server, waits for a client to connect,
|
||||
* and exchanges peer information (number of RX antennas) with the client.
|
||||
* The server writes its client_info (number of RX antennas) to a file, which the client reads.
|
||||
* The server does not wait for the client to write back; the client can connect at any point.
|
||||
*
|
||||
* @param peer_info The peer information to send to the client.
|
||||
* @return The peer information received from the client.
|
||||
*
|
||||
* @note This function will block until a client connects and the information is exchanged.
|
||||
* @param client_info The client information to publish.
|
||||
* @return The peer information (same as input, server is authoritative).
|
||||
*/
|
||||
static peer_info_t server_exchange_peer_info(peer_info_t peer_info)
|
||||
static void server_publish_client_info(client_info_t client_info, char *descriptor_file)
|
||||
{
|
||||
int server_fd, client_fd;
|
||||
|
||||
// Create a Unix domain socket
|
||||
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
AssertFatal(server_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
// Bind the socket to a file path
|
||||
struct sockaddr_un addr = {.sun_family = AF_UNIX};
|
||||
strncpy(addr.sun_path, "/tmp/vrtsim_socket", sizeof(addr.sun_path) - 1);
|
||||
unlink(addr.sun_path); // Ensure the path does not already exist
|
||||
int ret = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
|
||||
AssertFatal(ret == 0, "bind() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
// Listen for incoming connections
|
||||
ret = listen(server_fd, 1);
|
||||
AssertFatal(ret == 0, "listen() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
// Accept a connection from a client
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
client_fd = accept(server_fd, (struct sockaddr *)&addr, &addr_len);
|
||||
AssertFatal(client_fd >= 0, "accept() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
// Send the antenna info to the server
|
||||
ssize_t bytes_sent = send(client_fd, &peer_info, sizeof(peer_info), 0);
|
||||
AssertFatal(bytes_sent == sizeof(peer_info), "send() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
ssize_t bytes_received = recv(client_fd, &peer_info, sizeof(peer_info), 0);
|
||||
AssertFatal(bytes_received == sizeof(peer_info), "recv() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
LOG_I(HW, "Received client info: RX %d\n", peer_info.num_rx_antennas);
|
||||
|
||||
// Close the client and server sockets
|
||||
close(client_fd);
|
||||
close(server_fd);
|
||||
return peer_info;
|
||||
FILE *fp = fopen(descriptor_file, "wb");
|
||||
AssertFatal(fp != NULL, "Failed to open client info file for writing: %s\n", strerror(errno));
|
||||
size_t written = fwrite(&client_info, sizeof(client_info), 1, fp);
|
||||
AssertFatal(written == 1, "Failed to write client info to file\n");
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Exchanges peer information between the client and the server.
|
||||
*
|
||||
* This function connects to a Unix domain socket server, exchanges peer information
|
||||
* (number of RX antennas) with the server, and returns the peer information received
|
||||
* from the server.
|
||||
*
|
||||
* @param peer_info The peer information to send to the server.
|
||||
* @return The peer information received from the server.
|
||||
*
|
||||
* @note This function will block until the information is exchanged.
|
||||
*/
|
||||
static peer_info_t client_exchange_peer_info(peer_info_t peer_info)
|
||||
static client_info_t client_read_info(char *descriptor_file)
|
||||
{
|
||||
int client_fd;
|
||||
|
||||
// Create a Unix domain socket
|
||||
client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
AssertFatal(client_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
// Connect to the server
|
||||
struct sockaddr_un addr = {.sun_family = AF_UNIX};
|
||||
strncpy(addr.sun_path, "/tmp/vrtsim_socket", sizeof(addr.sun_path) - 1);
|
||||
int ret = -1;
|
||||
client_info_t client_info;
|
||||
int tries = 0;
|
||||
while (ret != 0 && tries < 10) {
|
||||
ret = connect(client_fd, (struct sockaddr *)&addr, sizeof(addr));
|
||||
tries++;
|
||||
while (tries < 10) {
|
||||
FILE *fp = fopen(descriptor_file, "rb");
|
||||
if (fp) {
|
||||
size_t read = fread(&client_info, sizeof(client_info), 1, fp);
|
||||
fclose(fp);
|
||||
if (read == 1) {
|
||||
return client_info;
|
||||
}
|
||||
}
|
||||
sleep(1);
|
||||
tries++;
|
||||
}
|
||||
AssertFatal(ret == 0, "connect() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
peer_info_t peer_info_received;
|
||||
ssize_t bytes_received = recv(client_fd, &peer_info_received, sizeof(peer_info_received), 0);
|
||||
AssertFatal(bytes_received == sizeof(peer_info_received), "recv() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
// Send the antenna info to the server
|
||||
ssize_t bytes_sent = send(client_fd, &peer_info, sizeof(peer_info), 0);
|
||||
AssertFatal(bytes_sent == sizeof(peer_info), "send() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
|
||||
LOG_I(HW, "Received peer info: RX %d\n", peer_info_received.num_rx_antennas);
|
||||
|
||||
// Close the client socket
|
||||
close(client_fd);
|
||||
return peer_info_received;
|
||||
AssertFatal(0, "Timeout waiting for client info\n");
|
||||
return client_info;
|
||||
}
|
||||
|
||||
static int vrtsim_connect(openair0_device *device)
|
||||
{
|
||||
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
|
||||
// Exchange peer info
|
||||
peer_info_t peer_info = {.num_rx_antennas = device->openair0_cfg[0].rx_num_channels};
|
||||
|
||||
// Setup a shared memory channel
|
||||
if (vrtsim_state->role == ROLE_SERVER) {
|
||||
vrtsim_state->peer_info = server_exchange_peer_info(peer_info);
|
||||
vrtsim_state->peer_info.num_rx_antennas = vrtsim_state->client_num_rx_antennas;
|
||||
vrtsim_state->channel = shm_td_iq_channel_create(DEFAULT_CHANNEL_NAME,
|
||||
vrtsim_state->peer_info.num_rx_antennas,
|
||||
device->openair0_cfg[0].rx_num_channels);
|
||||
// Exchange peer info
|
||||
client_info_t client_info = {
|
||||
.server_num_rx_antennas = device->openair0_cfg[0].rx_num_channels,
|
||||
.client_num_rx_antennas = vrtsim_state->client_num_rx_antennas,
|
||||
};
|
||||
server_publish_client_info(client_info, vrtsim_state->connection_descriptor);
|
||||
|
||||
vrtsim_state->run_timing_thread = true;
|
||||
int ret = pthread_create(&vrtsim_state->timing_thread, NULL, vrtsim_timing_job, vrtsim_state);
|
||||
AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
} else {
|
||||
vrtsim_state->peer_info = client_exchange_peer_info(peer_info);
|
||||
client_info_t client_info = client_read_info(vrtsim_state->connection_descriptor);
|
||||
AssertFatal(client_info.server_num_rx_antennas > 0, "Server did not publish valid client info, aborting client connection\n");
|
||||
AssertFatal(
|
||||
client_info.client_num_rx_antennas == device->openair0_cfg[0].rx_num_channels,
|
||||
"Server expects different number of RX antennas. %d != %d. Use server command line option --client-num-rx-antennas\n",
|
||||
client_info.client_num_rx_antennas,
|
||||
device->openair0_cfg[0].rx_num_channels);
|
||||
vrtsim_state->channel = shm_td_iq_channel_connect(DEFAULT_CHANNEL_NAME, 10);
|
||||
vrtsim_state->peer_info.num_rx_antennas = client_info.server_num_rx_antennas;
|
||||
vrtsim_state->last_received_sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel);
|
||||
}
|
||||
|
||||
// Handle channel modelling after number of RX antennas are known
|
||||
int num_tx_stats = 1;
|
||||
if (vrtsim_state->chanmod) {
|
||||
if (vrtsim_state->chanmod || vrtsim_state->taps_socket) {
|
||||
vrtsim_state->channel_modelling_actors = calloc_or_fail(vrtsim_state->peer_info.num_rx_antennas, sizeof(Actor_t));
|
||||
for (int i = 0; i < vrtsim_state->peer_info.num_rx_antennas; i++) {
|
||||
init_actor(&vrtsim_state->channel_modelling_actors[i], "chanmod", -1);
|
||||
}
|
||||
load_channel_model(vrtsim_state);
|
||||
if (vrtsim_state->taps_socket) {
|
||||
taps_client_connect(0,
|
||||
vrtsim_state->taps_socket,
|
||||
device->openair0_cfg[0].tx_num_channels,
|
||||
vrtsim_state->peer_info.num_rx_antennas,
|
||||
&vrtsim_state->channel_desc);
|
||||
} else {
|
||||
load_channel_model(vrtsim_state);
|
||||
}
|
||||
num_tx_stats = vrtsim_state->peer_info.num_rx_antennas;
|
||||
}
|
||||
vrtsim_state->tx_timing = calloc_or_fail(num_tx_stats, sizeof(tx_timing_t));
|
||||
@@ -326,22 +336,6 @@ static int vrtsim_connect(openair0_device *device)
|
||||
vrtsim_state->tx_timing[i].tx_histogram.range = 3000.0;
|
||||
}
|
||||
|
||||
// Setup a shared memory channel
|
||||
if (vrtsim_state->role == ROLE_SERVER) {
|
||||
vrtsim_state->channel = shm_td_iq_channel_create(vrtsim_state->channel_name,
|
||||
vrtsim_state->peer_info.num_rx_antennas,
|
||||
device->openair0_cfg[0].rx_num_channels);
|
||||
vrtsim_state->run_timing_thread = true;
|
||||
while (!shm_td_iq_channel_is_connected(vrtsim_state->channel)) {
|
||||
LOG_I(HW, "Waiting for client\n");
|
||||
sleep(1);
|
||||
}
|
||||
int ret = pthread_create(&vrtsim_state->timing_thread, NULL, vrtsim_timing_job, vrtsim_state);
|
||||
AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
} else {
|
||||
vrtsim_state->channel = shm_td_iq_channel_connect(vrtsim_state->channel_name, 10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -376,7 +370,7 @@ static int vrtsim_write_internal(vrtsim_state_t *vrtsim_state,
|
||||
typedef struct {
|
||||
vrtsim_state_t *vrtsim_state;
|
||||
openair0_timestamp timestamp;
|
||||
c16_t *samples[4];
|
||||
c16_t *samples[MAX_NUM_ANTENNAS_TX];
|
||||
int nsamps;
|
||||
int nbAnt;
|
||||
int flags;
|
||||
@@ -386,6 +380,7 @@ typedef struct {
|
||||
static void perform_channel_modelling(void *arg)
|
||||
{
|
||||
channel_modelling_args_t *channel_modelling_args = arg;
|
||||
vrtsim_state_t *vrtsim_state = channel_modelling_args->vrtsim_state;
|
||||
int nsamps = channel_modelling_args->nsamps;
|
||||
int aarx = channel_modelling_args->aarx;
|
||||
int nb_tx_ant = channel_modelling_args->nbAnt;
|
||||
@@ -396,31 +391,43 @@ static void perform_channel_modelling(void *arg)
|
||||
// Apply noise from global settings
|
||||
get_noise_vector((float *)samples, nsamps * 2);
|
||||
|
||||
channel_desc_t *channel_desc = channel_modelling_args->vrtsim_state->channel_desc;
|
||||
const float pathloss_linear = powf(10, channel_desc->path_loss_dB / 20.0);
|
||||
channel_desc_t *channel_desc = vrtsim_state->channel_desc;
|
||||
|
||||
if (channel_desc == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert channel impulse response to float + apply pathloss
|
||||
cf_t channel_impulse_response[nb_tx_ant][channel_desc->channel_length];
|
||||
for (int aatx = 0; aatx < nb_tx_ant; aatx++) {
|
||||
const struct complexd *channelModel = channel_desc->ch[aarx + (aatx * channel_desc->nb_rx)];
|
||||
for (int i = 0; i < channel_desc->channel_length; i++) {
|
||||
channel_impulse_response[aatx][i].r = channelModel[i].r * pathloss_linear;
|
||||
channel_impulse_response[aatx][i].i = channelModel[i].i * pathloss_linear;
|
||||
cf_t *channel_impulse_response_p[nb_tx_ant];
|
||||
if (!vrtsim_state->taps_socket) {
|
||||
const float pathloss_linear = powf(10, channel_desc->path_loss_dB / 20.0);
|
||||
// Convert channel impulse response to float + apply pathloss
|
||||
for (int aatx = 0; aatx < nb_tx_ant; aatx++) {
|
||||
const struct complexd *channelModel = channel_desc->ch[aarx + (aatx * channel_desc->nb_rx)];
|
||||
for (int i = 0; i < channel_desc->channel_length; i++) {
|
||||
channel_impulse_response[aatx][i].r = channelModel[i].r * pathloss_linear;
|
||||
channel_impulse_response[aatx][i].i = channelModel[i].i * pathloss_linear;
|
||||
}
|
||||
channel_impulse_response_p[aatx] = channel_impulse_response[aatx];
|
||||
}
|
||||
} else {
|
||||
for (int aatx = 0; aatx < nb_tx_ant; aatx++) {
|
||||
struct complexf *channelModel = channel_desc->ch_ps[aarx + (aatx * channel_desc->nb_rx)];
|
||||
channel_impulse_response_p[aatx] = channelModel;
|
||||
}
|
||||
}
|
||||
|
||||
for (int aatx = 0; aatx < nb_tx_ant; aatx++) {
|
||||
c16_t *previous_samples = saved_samples[aatx];
|
||||
for (int i = 0; i < nsamps; i++) {
|
||||
cf_t *impulse_response = channel_impulse_response[aatx];
|
||||
cf_t *impulse_response = channel_impulse_response_p[aatx];
|
||||
for (int l = 0; l < channel_desc->channel_length; l++) {
|
||||
int idx = i - l;
|
||||
// TODO: Use AVX512 for this
|
||||
// TODO: What are the previously sent samples (for impulse response)
|
||||
if (idx >= 0) {
|
||||
c16_t tx_input = input_samples[aatx][idx];
|
||||
samples[i].r += tx_input.r * impulse_response[l].r - tx_input.i * impulse_response[l].i;
|
||||
samples[i].i += tx_input.i * impulse_response[l].r + tx_input.r * impulse_response[l].i;
|
||||
}
|
||||
// TODO: Use AVX2 for this
|
||||
c16_t tx_input = idx >= 0 ? input_samples[aatx][idx]
|
||||
: previous_samples[(channel_modelling_args->timestamp + i + idx) % MAX_CHANNEL_LENGTH];
|
||||
samples[i].r += tx_input.r * impulse_response[l].r - tx_input.i * impulse_response[l].i;
|
||||
samples[i].i += tx_input.i * impulse_response[l].r + tx_input.r * impulse_response[l].i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -462,6 +469,7 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state,
|
||||
int nbAnt,
|
||||
int flags)
|
||||
{
|
||||
AssertFatal(nbAnt < MAX_NUM_ANTENNAS_TX, "Number of antennas %d exceeds maximum %d\n", nbAnt, MAX_NUM_ANTENNAS_TX);
|
||||
for (int aarx = 0; aarx < vrtsim_state->peer_info.num_rx_antennas; aarx++) {
|
||||
notifiedFIFO_elt_t *task = newNotifiedFIFO_elt(sizeof(channel_modelling_args_t), 0, NULL, perform_channel_modelling);
|
||||
channel_modelling_args_t *args = (channel_modelling_args_t *)NotifiedFifoData(task);
|
||||
@@ -476,21 +484,68 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state,
|
||||
}
|
||||
pushNotifiedFIFO(&vrtsim_state->channel_modelling_actors[aarx].fifo, task);
|
||||
}
|
||||
int start_index = timestamp % MAX_CHANNEL_LENGTH;
|
||||
int end_index = min(start_index + nsamps, MAX_CHANNEL_LENGTH);
|
||||
int cp_nsamps = end_index - start_index;
|
||||
for (int aatx = 0; aatx < nbAnt; aatx++) {
|
||||
c16_t *samples = (c16_t *)samplesVoid[aatx];
|
||||
memcpy(&saved_samples[aatx][start_index], &samples[0], sizeof(c16_t) * cp_nsamps);
|
||||
}
|
||||
|
||||
if (end_index < start_index + nsamps) {
|
||||
// wrap around condition, write at beginning of buffer
|
||||
cp_nsamps = nsamps - cp_nsamps; // remaining samples
|
||||
start_index = 0;
|
||||
for (int aatx = 0; aatx < nbAnt; aatx++) {
|
||||
c16_t *samples = (c16_t *)samplesVoid[aatx];
|
||||
memcpy(&saved_samples[aatx][start_index], &samples[0], sizeof(c16_t) * cp_nsamps);
|
||||
}
|
||||
}
|
||||
return nsamps;
|
||||
}
|
||||
|
||||
static int vrtsim_write(openair0_device *device, openair0_timestamp timestamp, void **samplesVoid, int nsamps, int nbAnt, int flags)
|
||||
{
|
||||
AssertFatal(nsamps > 0, "Number of samples must be greater than 0\n");
|
||||
AssertFatal(nbAnt > 0 && nbAnt <= MAX_NUM_ANTENNAS_TX,
|
||||
"Number of antennas %d must be between 1 and %d\n",
|
||||
nbAnt,
|
||||
MAX_NUM_ANTENNAS_TX);
|
||||
AssertFatal(timestamp >= 0, "Timestamp must be non-negative, got %ld\n", timestamp);
|
||||
timestamp -= device->openair0_cfg->command_line_sample_advance;
|
||||
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
|
||||
return vrtsim_state->chanmod ? vrtsim_write_with_chanmod(vrtsim_state, timestamp, samplesVoid, nsamps, nbAnt, flags)
|
||||
: vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0);
|
||||
bool channel_modelling = vrtsim_state->chanmod || vrtsim_state->taps_socket;
|
||||
return channel_modelling ? vrtsim_write_with_chanmod(vrtsim_state, timestamp, samplesVoid, nsamps, nbAnt, flags)
|
||||
: vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0);
|
||||
}
|
||||
|
||||
static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt)
|
||||
{
|
||||
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
|
||||
shm_td_iq_channel_wait(vrtsim_state->channel, vrtsim_state->last_received_sample + nsamps);
|
||||
if (shm_td_iq_channel_is_aborted(vrtsim_state->channel)) {
|
||||
return 0;
|
||||
}
|
||||
if (vrtsim_state->role == ROLE_SERVER) {
|
||||
uint64_t timeout_uS = 0; // 0 means no timeout
|
||||
shm_td_iq_channel_wait(vrtsim_state->channel, vrtsim_state->last_received_sample + nsamps, timeout_uS);
|
||||
} else {
|
||||
uint64_t start_sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel);
|
||||
uint64_t timeout_uS = 2 * 1000 * 1000; // 2 seconds timeout waiting for sample number to change
|
||||
//
|
||||
while (shm_td_iq_channel_wait(vrtsim_state->channel, vrtsim_state->last_received_sample + nsamps, timeout_uS) == 1) {
|
||||
uint64_t sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel);
|
||||
if (sample == start_sample) {
|
||||
LOG_E(HW,
|
||||
"VRTSIM: Read timeout waiting for sample %lu to change, aborting channel\n",
|
||||
vrtsim_state->last_received_sample + nsamps);
|
||||
shm_td_iq_channel_abort(vrtsim_state->channel);
|
||||
break;
|
||||
} else {
|
||||
start_sample = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ret = shm_td_iq_channel_rx(vrtsim_state->channel, vrtsim_state->last_received_sample, nsamps, 0, samplesVoid[0]);
|
||||
if (ret == CHANNEL_ERROR_TOO_LATE) {
|
||||
vrtsim_state->rx_samples_late += nsamps;
|
||||
@@ -506,14 +561,14 @@ static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp,
|
||||
static void vrtsim_end(openair0_device *device)
|
||||
{
|
||||
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
|
||||
if (vrtsim_state->role == ROLE_SERVER) {
|
||||
if (vrtsim_state->role == ROLE_SERVER && vrtsim_state->run_timing_thread) {
|
||||
vrtsim_state->run_timing_thread = false;
|
||||
int ret = pthread_join(vrtsim_state->timing_thread, NULL);
|
||||
AssertFatal(ret == 0, "pthread_join() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||
}
|
||||
|
||||
tx_timing_t *tx_timing = vrtsim_state->tx_timing;
|
||||
if (vrtsim_state->chanmod) {
|
||||
if (vrtsim_state->chanmod || vrtsim_state->taps_socket) {
|
||||
for (int i = 0; i < vrtsim_state->peer_info.num_rx_antennas; i++) {
|
||||
shutdown_actor(&vrtsim_state->channel_modelling_actors[i]);
|
||||
}
|
||||
@@ -526,9 +581,12 @@ static void vrtsim_end(openair0_device *device)
|
||||
tx_timing->tx_samples_total += tx_timing[i].tx_samples_total;
|
||||
}
|
||||
tx_timing->average_tx_budget /= vrtsim_state->peer_info.num_rx_antennas;
|
||||
free_noise_device();
|
||||
if (vrtsim_state->taps_socket) {
|
||||
taps_client_stop();
|
||||
}
|
||||
}
|
||||
// produce 1 second of extra samples so threads can finish
|
||||
shm_td_iq_channel_produce_samples(vrtsim_state->channel, vrtsim_state->sample_rate);
|
||||
shm_td_iq_channel_abort(vrtsim_state->channel);
|
||||
sleep(1);
|
||||
shm_td_iq_channel_destroy(vrtsim_state->channel);
|
||||
|
||||
@@ -540,9 +598,18 @@ static void vrtsim_end(openair0_device *device)
|
||||
"VRTSIM: Read/write too early (suspected radio implementaton error) TX: %lu, RX: %lu\n",
|
||||
tx_timing->tx_early,
|
||||
vrtsim_state->rx_early);
|
||||
LOG_I(HW, "VRTSIM: Average TX budget %.3lf uS\n", tx_timing->average_tx_budget);
|
||||
LOG_I(HW, "VRTSIM: Average TX budget %.3lf uS (more is better)\n", tx_timing->average_tx_budget);
|
||||
histogram_print(&tx_timing->tx_histogram);
|
||||
free(vrtsim_state->tx_timing);
|
||||
|
||||
if (vrtsim_state->role == ROLE_SERVER) {
|
||||
int ret = remove(vrtsim_state->connection_descriptor);
|
||||
if (ret != 0) {
|
||||
LOG_E(HW, "Failed to remove connection descriptor file %s: %s\n", vrtsim_state->connection_descriptor, strerror(errno));
|
||||
} else {
|
||||
LOG_A(HW, "Removed connection descriptor file %s\n", vrtsim_state->connection_descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int vrtsim_stub(openair0_device *device)
|
||||
@@ -588,10 +655,11 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device *devi
|
||||
vrtsim_state->tx_num_channels = openair0_cfg->tx_num_channels;
|
||||
vrtsim_state->rx_num_channels = openair0_cfg->rx_num_channels;
|
||||
|
||||
if (vrtsim_state->chanmod) {
|
||||
if (vrtsim_state->chanmod || vrtsim_state->taps_socket) {
|
||||
init_channelmod();
|
||||
int noise_power_dBFS = get_noise_power_dBFS();
|
||||
int16_t noise_power = noise_power_dBFS == INVALID_DBFS_VALUE ? 0 : (int16_t)(32767.0 / powf(10.0, .05 * -noise_power_dBFS));
|
||||
LOG_A(HW, "VRTSIM: Noise power %d sample value\n", noise_power);
|
||||
init_noise_device(noise_power);
|
||||
}
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user