From f3d144dd59ce32b1f47462a637de06895b4a9219 Mon Sep 17 00:00:00 2001 From: Robert Falkenberg Date: Tue, 16 Nov 2021 15:32:34 +0100 Subject: [PATCH] filerf: add tx, multi-channel, open via device string and test This commits extends the file-based RF device as follows: * open device via device string * add tx to file * add multi-channel support (multiple files) * add rf_file_test.c to for testing --- lib/src/phy/rf/CMakeLists.txt | 6 +- lib/src/phy/rf/rf_dev.h | 1 + lib/src/phy/rf/rf_file_imp.c | 363 ++++++++++++++++++++++++++----- lib/src/phy/rf/rf_file_imp.h | 8 +- lib/src/phy/rf/rf_file_imp_rx.c | 5 +- lib/src/phy/rf/rf_file_imp_trx.h | 24 +- lib/src/phy/rf/rf_file_imp_tx.c | 189 ++++++++++++++++ lib/src/phy/rf/rf_file_test.c | 312 ++++++++++++++++++++++++++ 8 files changed, 846 insertions(+), 62 deletions(-) create mode 100644 lib/src/phy/rf/rf_file_imp_tx.c create mode 100644 lib/src/phy/rf/rf_file_test.c diff --git a/lib/src/phy/rf/CMakeLists.txt b/lib/src/phy/rf/CMakeLists.txt index 348609de7..77d60bfff 100644 --- a/lib/src/phy/rf/CMakeLists.txt +++ b/lib/src/phy/rf/CMakeLists.txt @@ -59,7 +59,7 @@ if(RF_FOUND) list(APPEND SOURCES_RF rf_zmq_imp.c rf_zmq_imp_tx.c rf_zmq_imp_rx.c) endif (ZEROMQ_FOUND) - list(APPEND SOURCES_RF rf_file_imp.c rf_file_imp_rx.c) + list(APPEND SOURCES_RF rf_file_imp.c rf_file_imp_tx.c rf_file_imp_rx.c) add_library(srsran_rf_object OBJECT ${SOURCES_RF}) set_property(TARGET srsran_rf_object PROPERTY POSITION_INDEPENDENT_CODE 1) @@ -100,5 +100,9 @@ if(RF_FOUND) #add_test(rf_zmq_test rf_zmq_test) endif (ZEROMQ_FOUND) + add_executable(rf_file_test rf_file_test.c) + target_link_libraries(rf_file_test srsran_rf) + add_test(rf_file_test rf_file_test) + INSTALL(TARGETS srsran_rf DESTINATION ${LIBRARY_DIR}) endif(RF_FOUND) diff --git a/lib/src/phy/rf/rf_dev.h b/lib/src/phy/rf/rf_dev.h index 75f0db383..b31e9d65d 100644 --- a/lib/src/phy/rf/rf_dev.h +++ b/lib/src/phy/rf/rf_dev.h @@ -324,4 +324,5 @@ static rf_dev_t* available_devices[] = { #ifdef ENABLE_DUMMY_DEV &dev_dummy, #endif + &dev_file, NULL}; diff --git a/lib/src/phy/rf/rf_file_imp.c b/lib/src/phy/rf/rf_file_imp.c index f03295761..b44d97bcb 100644 --- a/lib/src/phy/rf/rf_file_imp.c +++ b/lib/src/phy/rf/rf_file_imp.c @@ -16,6 +16,7 @@ #include "rf_file_imp.h" #include "rf_file_imp_trx.h" #include "rf_helper.h" +#include #include #include #include @@ -35,18 +36,18 @@ typedef struct { uint32_t base_srate; uint32_t decim_factor; // decimation factor between base_srate used on transport on radio's rate double rx_gain; - uint32_t tx_freq_mhz[SRSRAN_MAX_PORTS]; - uint32_t rx_freq_mhz[SRSRAN_MAX_PORTS]; - bool tx_used; + uint32_t tx_freq_mhz[SRSRAN_MAX_CHANNELS]; + uint32_t rx_freq_mhz[SRSRAN_MAX_CHANNELS]; + bool tx_off; + char id[RF_PARAM_LEN]; // FILEs - rf_file_tx_t transmitter[SRSRAN_MAX_PORTS]; - rf_file_rx_t receiver[SRSRAN_MAX_PORTS]; - - char id[PARAM_LEN_SHORT]; + rf_file_tx_t transmitter[SRSRAN_MAX_CHANNELS]; + rf_file_rx_t receiver[SRSRAN_MAX_CHANNELS]; + bool close_files; // Various sample buffers - cf_t* buffer_decimation[SRSRAN_MAX_PORTS]; + cf_t* buffer_decimation[SRSRAN_MAX_CHANNELS]; cf_t* buffer_tx; // Rx timestamp @@ -55,6 +56,7 @@ typedef struct { pthread_mutex_t tx_config_mutex; pthread_mutex_t rx_config_mutex; pthread_mutex_t decim_mutex; + pthread_mutex_t rx_gain_mutex; } rf_file_handler_t; /* @@ -63,6 +65,31 @@ typedef struct { static void update_rates(rf_file_handler_t* handler, double srate); +void rf_file_info(char* id, const char* format, ...) +{ +#if VERBOSE + struct timeval t; + gettimeofday(&t, NULL); + va_list args; + va_start(args, format); + printf("[%s@%02ld.%06ld] ", id ? id : "file", t.tv_sec % 10, t.tv_usec); + vprintf(format, args); + va_end(args); +#else /* VERBOSE */ + // Do nothing +#endif /* VERBOSE */ +} + +void rf_file_error(char* id, const char* format, ...) +{ + struct timeval t; + gettimeofday(&t, NULL); + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + static inline int update_ts(void* h, uint64_t* ts, int nsamples, const char* dir) { int ret = SRSRAN_ERROR; @@ -74,6 +101,8 @@ static inline int update_ts(void* h, uint64_t* ts, int nsamples, const char* dir srsran_timestamp_t _ts = {}; srsran_timestamp_init_uint64(&_ts, *ts, handler->base_srate); + rf_file_info( + handler->id, " -> next %s time after %d samples: %d + %.3f\n", dir, nsamples, _ts.full_secs, _ts.frac_secs); ret = SRSRAN_SUCCESS; } @@ -138,11 +167,76 @@ int rf_file_open(char* args, void** h) int rf_file_open_multi(char* args, void** h, uint32_t nof_channels) { - perror("Cannot open file-based RF as regular device. Use rf_file_open_file() instead."); - return SRSRAN_ERROR_INVALID_COMMAND; + int ret = SRSRAN_ERROR; + + FILE* rx_files[SRSRAN_MAX_CHANNELS] = {NULL}; + FILE* tx_files[SRSRAN_MAX_CHANNELS] = {NULL}; + + if (h && nof_channels < SRSRAN_MAX_CHANNELS) { + uint32_t base_srate = FILE_BASERATE_DEFAULT_HZ; + + // parse args + if (args && strlen(args)) { + // base_srate + parse_uint32(args, "base_srate", -1, &base_srate); + } else { + fprintf(stderr, "[file] Error: RF device args are required for file-based no-RF module\n"); + goto clean_exit; + } + + for (int i = 0; i < nof_channels; i++) { + // rx_file + char rx_file[RF_PARAM_LEN] = {}; + parse_string(args, "rx_file", i, rx_file); + + // tx_file + char tx_file[RF_PARAM_LEN] = {}; + parse_string(args, "tx_file", i, tx_file); + + // initialize transmitter + if (strlen(tx_file) != 0) { + tx_files[i] = fopen(tx_file, "wb"); + if (tx_files[i] == NULL) { + fprintf(stderr, "[file] Error: opening tx_file%d: %s; %s\n", i, tx_file, strerror(errno)); + goto clean_exit; + } + } + + // initialize receiver + if (strlen(rx_file) != 0) { + rx_files[i] = fopen(rx_file, "rb"); + if (rx_files[i] == NULL) { + fprintf(stderr, "[file] Error: opening rx_file%d: %s; %s\n", i, rx_file, strerror(errno)); + goto clean_exit; + } + } + } + + // defer further initialization to open_file method + ret = rf_file_open_file(h, rx_files, tx_files, nof_channels, base_srate); + if (ret != SRSRAN_SUCCESS) { + goto clean_exit; + } + + // add flag to close all files when closing device + rf_file_handler_t* handler = (rf_file_handler_t*)(*h); + handler->close_files = true; + return ret; + } + +clean_exit: + for (int i = 0; i < nof_channels; i++) { + if (rx_files[i] != NULL) { + fclose(rx_files[i]); + } + if (tx_files[i] != NULL) { + fclose(tx_files[i]); + } + } + return ret; } -int rf_file_open_file(void** h, FILE **rx_files, FILE **tx_files, uint32_t nof_channels, uint32_t base_srate) +int rf_file_open_file(void** h, FILE** rx_files, FILE** tx_files, uint32_t nof_channels, uint32_t base_srate) { int ret = SRSRAN_ERROR; @@ -151,13 +245,12 @@ int rf_file_open_file(void** h, FILE **rx_files, FILE **tx_files, uint32_t nof_c rf_file_handler_t* handler = (rf_file_handler_t*)malloc(sizeof(rf_file_handler_t)); if (!handler) { - perror("malloc"); + fprintf(stderr, "malloc: %s\n", strerror(errno)); return SRSRAN_ERROR; } memset(handler, 0, sizeof(rf_file_handler_t)); *h = handler; - handler->base_srate = FILE_BASERATE_DEFAULT_HZ; // Sample rate for 100 PRB cell - handler->rx_gain = 0.0; + handler->base_srate = base_srate; handler->info.max_rx_gain = FILE_MAX_GAIN_DB; handler->info.min_rx_gain = FILE_MIN_GAIN_DB; handler->info.max_tx_gain = FILE_MAX_GAIN_DB; @@ -171,17 +264,21 @@ int rf_file_open_file(void** h, FILE **rx_files, FILE **tx_files, uint32_t nof_c rx_opts.id = handler->id; if (pthread_mutex_init(&handler->tx_config_mutex, NULL)) { - perror("Mutex init"); + fprintf(stderr, "Mutex init: %s\n", strerror(errno)); } if (pthread_mutex_init(&handler->rx_config_mutex, NULL)) { - perror("Mutex init"); + fprintf(stderr, "Mutex init: %s\n", strerror(errno)); } if (pthread_mutex_init(&handler->decim_mutex, NULL)) { - perror("Mutex init"); + fprintf(stderr, "Mutex init: %s\n", strerror(errno)); + } + if (pthread_mutex_init(&handler->rx_gain_mutex, NULL)) { + fprintf(stderr, "Mutex init: %s\n", strerror(errno)); } - // base_srate - handler->base_srate = base_srate; + pthread_mutex_lock(&handler->rx_gain_mutex); + handler->rx_gain = 0.0; + pthread_mutex_unlock(&handler->rx_gain_mutex); // id // TODO: set some meaningful ID in handler->id @@ -195,29 +292,30 @@ int rf_file_open_file(void** h, FILE **rx_files, FILE **tx_files, uint32_t nof_c // Create channels for (int i = 0; i < handler->nof_channels; i++) { - if (rx_files != NULL) { + if (rx_files != NULL && rx_files[i] != NULL) { rx_opts.file = rx_files[i]; if (rf_file_rx_open(&handler->receiver[i], rx_opts) != SRSRAN_SUCCESS) { fprintf(stderr, "[file] Error: opening receiver\n"); goto clean_exit; } } else { - fprintf(stdout, "[file] %s Rx file not specified. Disabling receiver.\n", handler->id); + // no rx_files provided + fprintf(stdout, "[file] %s rx channel %d not specified. Disabling receiver.\n", handler->id, i); } - if (tx_files != NULL) { + if (tx_files != NULL && tx_files[i] != NULL) { tx_opts.file = tx_files[i]; - // TX is not implemented yet - // if(rf_file_tx_open(&handler->transmitter[i], tx_opts) != SRSRAN_SUCCESS) { - // fprintf(stderr, "[file] Error: opening transmitter\n"); - // goto clean_exit; - // } + if (rf_file_tx_open(&handler->transmitter[i], tx_opts) != SRSRAN_SUCCESS) { + fprintf(stderr, "[file] Error: opening transmitter\n"); + goto clean_exit; + } } else { // no tx_files provided - fprintf(stdout, "[file] %s Tx file not specified. Disabling transmitter.\n", handler->id); + fprintf(stdout, "[file] %s tx channel %d not specified. Disabling transmitter.\n", handler->id, i); + handler->tx_off = true; } if (!handler->transmitter[i].running && !handler->receiver[i].running) { - fprintf(stderr, "[file] Error: Neither Tx file nor Rx file specified.\n"); + fprintf(stderr, "[file] Error: Neither tx nor rx specificed for channel %d.\n", i); goto clean_exit; } } @@ -249,9 +347,15 @@ int rf_file_open_file(void** h, FILE **rx_files, FILE **tx_files, uint32_t nof_c int rf_file_close(void* h) { - rf_file_handler_t* handler = (rf_file_handler_t*)h; + rf_file_info(handler->id, "Closing ...\n"); + + for (int i = 0; i < handler->nof_channels; i++) { + rf_file_tx_close(&handler->transmitter[i]); + rf_file_rx_close(&handler->receiver[i]); + } + for (uint32_t i = 0; i < handler->nof_channels; i++) { if (handler->buffer_decimation[i]) { free(handler->buffer_decimation[i]); @@ -265,6 +369,18 @@ int rf_file_close(void* h) pthread_mutex_destroy(&handler->tx_config_mutex); pthread_mutex_destroy(&handler->rx_config_mutex); pthread_mutex_destroy(&handler->decim_mutex); + pthread_mutex_destroy(&handler->rx_gain_mutex); + + if (handler->close_files) { + for (int i = 0; i < handler->nof_channels; i++) { + if (handler->receiver[i].file != NULL) { + fclose(handler->receiver[i].file); + } + if (handler->transmitter[i].file != NULL) { + fclose(handler->transmitter[i].file); + } + } + } // Free all free(handler); @@ -412,13 +528,11 @@ void rf_file_get_time(void* h, time_t* secs, double* frac_secs) int rf_file_recv_with_time(void* h, void* data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs) { - void* data_multi[SRSRAN_MAX_PORTS] = {NULL}; - data_multi[0] = data; - return rf_file_recv_with_time_multi(h, data_multi, nsamples, blocking, secs, frac_secs); + return rf_file_recv_with_time_multi(h, &data, nsamples, blocking, secs, frac_secs); } int rf_file_recv_with_time_multi(void* h, - void* data[SRSRAN_MAX_PORTS], + void** data, uint32_t nsamples, bool blocking, time_t* secs, @@ -431,23 +545,28 @@ int rf_file_recv_with_time_multi(void* h, // Map ports to data buffers according to the selected frequencies pthread_mutex_lock(&handler->rx_config_mutex); - cf_t* buffers[SRSRAN_MAX_PORTS] = {}; // Buffer pointers, NULL if unmatched - for (uint32_t i = 0; i < handler->nof_channels; i++) { - bool mapped = false; + bool mapped[SRSRAN_MAX_CHANNELS] = {}; // Mapped mask, set to true when the physical channel is used + cf_t* buffers[SRSRAN_MAX_CHANNELS] = {}; // Buffer pointers, NULL if unmatched - // Find first matching frequency - for (uint32_t j = 0; j < handler->nof_channels && !mapped; j++) { - // Traverse all channels, break if mapped - if (buffers[j] == NULL && rf_file_rx_match_freq(&handler->receiver[j], handler->rx_freq_mhz[i])) { - // Available buffer and matched frequency with receiver - buffers[j] = (cf_t*)data[i]; - mapped = true; + // For each logical channel... + for (uint32_t logical = 0; logical < handler->nof_channels; logical++) { + bool unmatched = true; + + // For each physical channel... + for (uint32_t physical = 0; physical < handler->nof_channels; physical++) { + // Consider a match if the physical channel is NOT mapped and the frequency match + if (!mapped[physical] && rf_file_rx_match_freq(&handler->receiver[physical], handler->rx_freq_mhz[logical])) { + // Not mapped and matched frequency with receiver + buffers[physical] = (cf_t*)data[logical]; + mapped[physical] = true; + unmatched = false; + break; } } // If no matching frequency found; set data to zeros - if (!mapped && data[i]) { - memset(data[i], 0, sizeof(cf_t) * nsamples); + if (unmatched) { + srsran_vec_zero(data[logical], nsamples); } } pthread_mutex_unlock(&handler->rx_config_mutex); @@ -460,6 +579,8 @@ int rf_file_recv_with_time_multi(void* h, uint32_t nbytes = NSAMPLES2NBYTES(nsamples * decim_factor); uint32_t nsamples_baserate = nsamples * decim_factor; + rf_file_info(handler->id, "Rx %d samples (%d B)\n", nsamples, nbytes); + // set timestamp for this reception if (secs != NULL && frac_secs != NULL) { srsran_timestamp_t ts = {}; @@ -488,10 +609,19 @@ int rf_file_recv_with_time_multi(void* h, srsran_timestamp_t ts_tx = {}, ts_rx = {}; srsran_timestamp_init_uint64(&ts_tx, handler->transmitter[0].nsamples, handler->base_srate); srsran_timestamp_init_uint64(&ts_rx, handler->next_rx_ts, handler->base_srate); + rf_file_info(handler->id, " - next rx time: %d + %.3f\n", ts_rx.full_secs, ts_rx.frac_secs); + rf_file_info(handler->id, " - next tx time: %d + %.3f\n", ts_tx.full_secs, ts_tx.frac_secs); + + // check for tx gap if we're also transmitting on this radio + for (int i = 0; i < handler->nof_channels; i++) { + if (handler->transmitter[i].running) { + rf_file_tx_align(&handler->transmitter[i], handler->next_rx_ts + nsamples_baserate); + } + } // copy from rx buffer as many samples as requested into provided buffer - bool completed = false; - int32_t count[SRSRAN_MAX_PORTS] = {}; + bool completed = false; + int32_t count[SRSRAN_MAX_CHANNELS] = {}; while (!completed) { uint32_t completed_count = 0; @@ -523,6 +653,7 @@ int rf_file_recv_with_time_multi(void* h, // Check if all channels are completed completed = (completed_count == handler->nof_channels); } + rf_file_info(handler->id, " - read %d samples.\n", NBYTES2NSAMPLES(nbytes)); // decimate if needed if (decim_factor != 1) { @@ -538,14 +669,24 @@ int rf_file_recv_with_time_multi(void* h, for (int j = 0; j < decim_factor; j++, n++) { avg += ptr[n]; } - dst[i] = avg; + dst[i] = avg; // divide by decim_factor later via scale } + + rf_file_info(handler->id, + " - re-adjust bytes due to %dx decimation %d --> %d samples)\n", + decim_factor, + nsamples_baserate, + nsamples); } } } // Set gain + pthread_mutex_lock(&handler->rx_gain_mutex); float scale = srsran_convert_dB_to_amplitude(handler->rx_gain); + pthread_mutex_unlock(&handler->rx_gain_mutex); + // scale shall also incorporate decim_factor + scale = scale / decim_factor; for (uint32_t c = 0; c < handler->nof_channels; c++) { if (buffers[c]) { srsran_vec_sc_prod_cfc(buffers[c], scale, buffers[c], nsamples); @@ -589,9 +730,127 @@ int rf_file_send_timed_multi(void* h, bool is_start_of_burst, bool is_end_of_burst) { - // Not implemented - fprintf(stderr, "Error: rf_file_send_timed_multi not implemented.\n"); - return SRSRAN_ERROR; + int ret = SRSRAN_ERROR; + + if (h && data && nsamples > 0) { + rf_file_handler_t* handler = (rf_file_handler_t*)h; + + // Map ports to data buffers according to the selected frequencies + pthread_mutex_lock(&handler->tx_config_mutex); + bool mapped[SRSRAN_MAX_CHANNELS] = {}; // Mapped mask, set to true when the physical channel is used + cf_t* buffers[SRSRAN_MAX_CHANNELS] = {}; // Buffer pointers, NULL if unmatched or zero transmission + + // For each logical channel... + for (uint32_t logical = 0; logical < handler->nof_channels; logical++) { + // For each physical channel... + for (uint32_t physical = 0; physical < handler->nof_channels; physical++) { + // Consider a match if the physical channel is NOT mapped and the frequency match + if (!mapped[physical] && + rf_file_tx_match_freq(&handler->transmitter[physical], handler->tx_freq_mhz[logical])) { + // Not mapped and matched frequency with receiver + buffers[physical] = (cf_t*)data[logical]; + mapped[physical] = true; + break; + } + } + } + pthread_mutex_unlock(&handler->tx_config_mutex); + + // Protect the access to decim_factor since is a shared variable + pthread_mutex_lock(&handler->decim_mutex); + uint32_t decim_factor = handler->decim_factor; + pthread_mutex_unlock(&handler->decim_mutex); + + uint32_t nbytes = NSAMPLES2NBYTES(nsamples); + uint32_t nsamples_baseband = nsamples * decim_factor; + uint32_t nbytes_baseband = NSAMPLES2NBYTES(nsamples_baseband); + if (nbytes_baseband > FILE_MAX_BUFFER_SIZE) { + fprintf(stderr, "Error: trying to transmit too many samples (%d > %zu).\n", nbytes, FILE_MAX_BUFFER_SIZE); + goto clean_exit; + } + + rf_file_info(handler->id, "Tx %d samples (%d B)\n", nsamples, nbytes); + + // return if transmitter is switched off + if (handler->tx_off) { + return SRSRAN_SUCCESS; + } + + // check if this is a tx in the future + if (has_time_spec) { + rf_file_info(handler->id, " - tx time: %d + %.3f\n", secs, frac_secs); + + srsran_timestamp_t ts = {}; + srsran_timestamp_init(&ts, secs, frac_secs); + uint64_t tx_ts = srsran_timestamp_uint64(&ts, handler->base_srate); + int num_tx_gap_samples = 0; + + for (int i = 0; i < handler->nof_channels; i++) { + if (handler->transmitter[i].running) { + num_tx_gap_samples = rf_file_tx_align(&handler->transmitter[i], tx_ts); + } + } + + if (num_tx_gap_samples < 0) { + fprintf(stderr, + "[file] Error: tx time is %.3f ms in the past (%" PRIu64 " < %" PRIu64 ")\n", + -1000.0 * num_tx_gap_samples / handler->base_srate, + tx_ts, + handler->transmitter[0].nsamples); + goto clean_exit; + } + } + + // Send base-band samples + for (int i = 0; i < handler->nof_channels; i++) { + if (buffers[i] != NULL) { + // Select buffer pointer depending on interpolation + cf_t* buf = (decim_factor != 1) ? handler->buffer_tx : buffers[i]; + + // Interpolate if required + if (decim_factor != 1) { + rf_file_info(handler->id, + " - re-adjust bytes due to %dx interpolation %d --> %d samples)\n", + decim_factor, + nsamples, + nsamples_baseband); + + int n = 0; + cf_t* src = buffers[i]; + for (int k = 0; k < nsamples; k++) { + // perform zero order hold + for (int j = 0; j < decim_factor; j++, n++) { + buf[n] = src[k]; + } + } + + if (nsamples_baseband != n) { + fprintf(stderr, + "Number of tx samples (%d) does not match with number of interpolated samples (%d)\n", + nsamples_baseband, + n); + goto clean_exit; + } + } + + int n = rf_file_tx_baseband(&handler->transmitter[i], buf, nsamples_baseband); + if (n == SRSRAN_ERROR) { + goto clean_exit; + } + } else { + int n = rf_file_tx_zeros(&handler->transmitter[i], nsamples_baseband); + if (n == SRSRAN_ERROR) { + goto clean_exit; + } + } + } + } + + ret = SRSRAN_SUCCESS; + +clean_exit: + + return ret; } #endif diff --git a/lib/src/phy/rf/rf_file_imp.h b/lib/src/phy/rf/rf_file_imp.h index ae2763b7a..5db7e697f 100644 --- a/lib/src/phy/rf/rf_file_imp.h +++ b/lib/src/phy/rf/rf_file_imp.h @@ -95,12 +95,8 @@ SRSRAN_API void rf_file_get_time(void* h, time_t* secs, double* frac_secs); SRSRAN_API int rf_file_recv_with_time(void* h, void* data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs); -SRSRAN_API int rf_file_recv_with_time_multi(void* h, - void* data[SRSRAN_MAX_PORTS], - uint32_t nsamples, - bool blocking, - time_t* secs, - double* frac_secs); +SRSRAN_API int +rf_file_recv_with_time_multi(void* h, void** data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs); SRSRAN_API int rf_file_send_timed(void* h, void* data, diff --git a/lib/src/phy/rf/rf_file_imp_rx.c b/lib/src/phy/rf/rf_file_imp_rx.c index 30ceb9807..57dc98384 100644 --- a/lib/src/phy/rf/rf_file_imp_rx.c +++ b/lib/src/phy/rf/rf_file_imp_rx.c @@ -21,7 +21,7 @@ int rf_file_rx_open(rf_file_rx_t* q, rf_file_opts_t opts) if (q) { // Zero object - bzero(q, sizeof(rf_file_rx_t)); + memset(q, 0, sizeof(rf_file_rx_t)); // Copy id strncpy(q->id, opts.id, FILE_ID_STRLEN - 1); @@ -62,7 +62,7 @@ clean_exit: int rf_file_rx_baseband(rf_file_rx_t* q, cf_t* buffer, uint32_t nsamples) { - uint32_t sample_sz = sizeof(cf_t); + uint32_t sample_sz = sizeof(cf_t); int ret = fread(buffer, sample_sz, nsamples, q->file); if (ret > 0) { @@ -83,6 +83,7 @@ bool rf_file_rx_match_freq(rf_file_rx_t* q, uint32_t freq_hz) void rf_file_rx_close(rf_file_rx_t* q) { + rf_file_info(q->id, "Closing ...\n"); q->running = false; if (q->temp_buffer) { diff --git a/lib/src/phy/rf/rf_file_imp_trx.h b/lib/src/phy/rf/rf_file_imp_trx.h index 5c4d596a1..4e5cc63a8 100644 --- a/lib/src/phy/rf/rf_file_imp_trx.h +++ b/lib/src/phy/rf/rf_file_imp_trx.h @@ -42,7 +42,8 @@ typedef struct { pthread_mutex_t mutex; cf_t* zeros; void* temp_buffer_convert; - uint32_t frequency_hz_mhz; + uint32_t frequency_mhz; + int32_t sample_offset; } rf_file_tx_t; typedef struct { @@ -68,8 +69,29 @@ typedef struct { /* * Common functions */ +SRSRAN_API void rf_file_info(char* id, const char* format, ...); + +SRSRAN_API void rf_file_error(char* id, const char* format, ...); + SRSRAN_API int rf_file_handle_error(char* id, const char* text); +/* + * Transmitter functions + */ +SRSRAN_API int rf_file_tx_open(rf_file_tx_t* q, rf_file_opts_t opts); + +SRSRAN_API int rf_file_tx_align(rf_file_tx_t* q, uint64_t ts); + +SRSRAN_API int rf_file_tx_baseband(rf_file_tx_t* q, cf_t* buffer, uint32_t nsamples); + +SRSRAN_API int rf_file_tx_get_nsamples(rf_file_tx_t* q); + +SRSRAN_API int rf_file_tx_zeros(rf_file_tx_t* q, uint32_t nsamples); + +SRSRAN_API bool rf_file_tx_match_freq(rf_file_tx_t* q, uint32_t freq_hz); + +SRSRAN_API void rf_file_tx_close(rf_file_tx_t* q); + /* * Receiver functions */ diff --git a/lib/src/phy/rf/rf_file_imp_tx.c b/lib/src/phy/rf/rf_file_imp_tx.c new file mode 100644 index 000000000..d3398048b --- /dev/null +++ b/lib/src/phy/rf/rf_file_imp_tx.c @@ -0,0 +1,189 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "rf_file_imp_trx.h" +#include +#include +#include +#include +#include +#include + +int rf_file_tx_open(rf_file_tx_t* q, rf_file_opts_t opts) +{ + int ret = SRSRAN_ERROR; + + if (q) { + // Zero object + memset(q, 0, sizeof(rf_file_tx_t)); + + // Copy id + strncpy(q->id, opts.id, FILE_ID_STRLEN - 1); + q->id[FILE_ID_STRLEN - 1] = '\0'; + + // Assign file + q->file = opts.file; + + // Configure formats + q->sample_format = opts.sample_format; + q->frequency_mhz = opts.frequency_mhz; + + q->temp_buffer_convert = srsran_vec_malloc(FILE_MAX_BUFFER_SIZE); + if (!q->temp_buffer_convert) { + fprintf(stderr, "Error: allocating tx buffer\n"); + goto clean_exit; + } + + if (pthread_mutex_init(&q->mutex, NULL)) { + fprintf(stderr, "Error: creating mutex\n"); + goto clean_exit; + } + + q->zeros = srsran_vec_malloc(FILE_MAX_BUFFER_SIZE); + if (!q->zeros) { + fprintf(stderr, "Error: allocating zeros\n"); + goto clean_exit; + } + memset(q->zeros, 0, FILE_MAX_BUFFER_SIZE); + + q->running = true; + + ret = SRSRAN_SUCCESS; + } + +clean_exit: + return ret; +} + +static int _rf_file_tx_baseband(rf_file_tx_t* q, cf_t* buffer, uint32_t nsamples) +{ + int n = SRSRAN_ERROR; + + // convert samples if necessary + void* buf = (buffer) ? buffer : q->zeros; + uint32_t sample_sz = sizeof(cf_t); + + if (q->sample_format == FILERF_TYPE_SC16) { + buf = q->temp_buffer_convert; + sample_sz = 2 * sizeof(short); + srsran_vec_convert_fi((float*)buffer, INT16_MAX, (short*)q->temp_buffer_convert, 2 * nsamples); + } + + size_t ret = fwrite(buf, (size_t)sample_sz, (size_t)nsamples, q->file); + if (ret < (size_t)nsamples) { + rf_file_error(q->id, + "[file] Error: transmitter expected %d bytes and sent %d. %s.\n", + NSAMPLES2NBYTES(nsamples), + ret, + strerror(errno)); + n = SRSRAN_ERROR; + goto clean_exit; + } + + // Increment sample counter + q->nsamples += nsamples; + n = nsamples; + +clean_exit: + return n; +} + +int rf_file_tx_align(rf_file_tx_t* q, uint64_t ts) +{ + pthread_mutex_lock(&q->mutex); + + int64_t nsamples = (int64_t)ts - (int64_t)q->nsamples; + + if (nsamples > 0) { + rf_file_info(q->id, " - Detected Tx gap of %d samples.\n", nsamples); + _rf_file_tx_baseband(q, q->zeros, (uint32_t)nsamples); + } + + pthread_mutex_unlock(&q->mutex); + + return (int)nsamples; +} + +int rf_file_tx_baseband(rf_file_tx_t* q, cf_t* buffer, uint32_t nsamples) +{ + int n; + + pthread_mutex_lock(&q->mutex); + + if (q->sample_offset > 0) { + _rf_file_tx_baseband(q, q->zeros, (uint32_t)q->sample_offset); + q->sample_offset = 0; + } else if (q->sample_offset < 0) { + n = SRSRAN_MIN(-q->sample_offset, nsamples); + buffer += n; + nsamples -= n; + q->sample_offset += n; + if (nsamples == 0) { + return n; + } + } + + n = _rf_file_tx_baseband(q, buffer, nsamples); + + pthread_mutex_unlock(&q->mutex); + + return n; +} + +int rf_file_tx_get_nsamples(rf_file_tx_t* q) +{ + pthread_mutex_lock(&q->mutex); + int ret = q->nsamples; + pthread_mutex_unlock(&q->mutex); + return ret; +} + +int rf_file_tx_zeros(rf_file_tx_t* q, uint32_t nsamples) +{ + pthread_mutex_lock(&q->mutex); + + rf_file_info(q->id, " - Tx %d Zeros.\n", nsamples); + _rf_file_tx_baseband(q, q->zeros, (uint32_t)nsamples); + + pthread_mutex_unlock(&q->mutex); + + return (int)nsamples; +} + +bool rf_file_tx_match_freq(rf_file_tx_t* q, uint32_t freq_hz) +{ + bool ret = false; + if (q) { + ret = (q->frequency_mhz == 0 || q->frequency_mhz == freq_hz); + } + return ret; +} + +void rf_file_tx_close(rf_file_tx_t* q) +{ + rf_file_info(q->id, "Closing ...\n"); + pthread_mutex_lock(&q->mutex); + q->running = false; + pthread_mutex_unlock(&q->mutex); + + pthread_mutex_destroy(&q->mutex); + + if (q->zeros) { + free(q->zeros); + } + + if (q->temp_buffer_convert) { + free(q->temp_buffer_convert); + } + + q->file = NULL; +} diff --git a/lib/src/phy/rf/rf_file_test.c b/lib/src/phy/rf/rf_file_test.c new file mode 100644 index 000000000..a16ea2821 --- /dev/null +++ b/lib/src/phy/rf/rf_file_test.c @@ -0,0 +1,312 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "rf_file_imp.h" +#include "srsran/common/tsan_options.h" +#include "srsran/phy/common/timestamp.h" +#include "srsran/phy/utils/debug.h" +#include +#include +#include +#include +#include + +#define PRINT_SAMPLES 0 +#define COMPARE_BITS 0 +#define COMPARE_EPSILON (1e-6f) +#define NOF_RX_ANT 4 +#define NUM_SF (500) +#define SF_LEN (1920) +#define RF_BUFFER_SIZE (SF_LEN * NUM_SF) +#define TX_OFFSET_MS (4) + +static cf_t ue_rx_buffer[NOF_RX_ANT][RF_BUFFER_SIZE]; +static cf_t enb_tx_buffer[NOF_RX_ANT][RF_BUFFER_SIZE]; +static cf_t enb_rx_buffer[NOF_RX_ANT][RF_BUFFER_SIZE]; + +static srsran_rf_t ue_radio, enb_radio; +pthread_t rx_thread; + +void* ue_rx_thread_function(void* args) +{ + char rf_args[RF_PARAM_LEN]; + strncpy(rf_args, (char*)args, RF_PARAM_LEN - 1); + rf_args[RF_PARAM_LEN - 1] = 0; + + // sleep(1); + + printf("opening rx device with args=%s\n", rf_args); + if (srsran_rf_open_devname(&ue_radio, "file", rf_args, NOF_RX_ANT)) { + fprintf(stderr, "Error opening rf\n"); + exit(-1); + } + + // receive 5 subframes at once (i.e. mimic initial rx that receives one slot) + uint32_t num_slots = NUM_SF / 5; + uint32_t num_samps_per_slot = SF_LEN * 5; + uint32_t num_rxed_samps = 0; + for (uint32_t i = 0; i < num_slots; ++i) { + void* data_ptr[SRSRAN_MAX_PORTS] = {NULL}; + for (uint32_t c = 0; c < NOF_RX_ANT; c++) { + data_ptr[c] = &ue_rx_buffer[c][i * num_samps_per_slot]; + } + num_rxed_samps += srsran_rf_recv_with_time_multi(&ue_radio, data_ptr, num_samps_per_slot, true, NULL, NULL); + } + + printf("received %d samples.\n", num_rxed_samps); + + printf("closing ue rx device\n"); + srsran_rf_close(&ue_radio); + + return NULL; +} + +void enb_tx_function(const char* tx_args, bool timed_tx) +{ + char rf_args[RF_PARAM_LEN]; + strncpy(rf_args, tx_args, RF_PARAM_LEN - 1); + rf_args[RF_PARAM_LEN - 1] = 0; + + printf("opening tx device with args=%s\n", rf_args); + if (srsran_rf_open_devname(&enb_radio, "file", rf_args, NOF_RX_ANT)) { + fprintf(stderr, "Error opening rf\n"); + exit(-1); + } + + // generate random tx data + for (int c = 0; c < NOF_RX_ANT; c++) { + for (int i = 0; i < RF_BUFFER_SIZE; i++) { + enb_tx_buffer[c][i] = ((float)rand() / (float)RAND_MAX) + _Complex_I * ((float)rand() / (float)RAND_MAX); + } + } + + // send data subframe per subframe + uint32_t num_txed_samples = 0; + + // initial transmission without ts + void* data_ptr[SRSRAN_MAX_PORTS] = {NULL}; + for (int c = 0; c < NOF_RX_ANT; c++) { + data_ptr[c] = &enb_tx_buffer[c][num_txed_samples]; + } + int ret = srsran_rf_send_multi(&enb_radio, (void**)data_ptr, SF_LEN, true, true, false); + num_txed_samples += SF_LEN; + + // from here on, all transmissions are timed relative to the last rx time + srsran_timestamp_t rx_time, tx_time; + + for (uint32_t i = 0; i < NUM_SF - ((timed_tx) ? TX_OFFSET_MS : 1); ++i) { + // first recv samples + for (int c = 0; c < NOF_RX_ANT; c++) { + data_ptr[c] = enb_rx_buffer[c]; + } + srsran_rf_recv_with_time_multi(&enb_radio, data_ptr, SF_LEN, true, &rx_time.full_secs, &rx_time.frac_secs); + + // prepare data buffer + for (int c = 0; c < NOF_RX_ANT; c++) { + data_ptr[c] = &enb_tx_buffer[c][num_txed_samples]; + } + + if (timed_tx) { + // timed tx relative to receive time (this will cause a cap in the rx'ed samples at the UE resulting in 3 zero + // subframes) + srsran_timestamp_copy(&tx_time, &rx_time); + srsran_timestamp_add(&tx_time, 0, TX_OFFSET_MS * 1e-3); + ret = srsran_rf_send_timed_multi( + &enb_radio, (void**)data_ptr, SF_LEN, tx_time.full_secs, tx_time.frac_secs, true, true, false); + } else { + // normal tx + ret = srsran_rf_send_multi(&enb_radio, (void**)data_ptr, SF_LEN, true, true, false); + } + if (ret != SRSRAN_SUCCESS) { + fprintf(stderr, "Error sending data\n"); + exit(-1); + } + + num_txed_samples += SF_LEN; + } + + printf("transmitted %d samples in %d subframes\n", num_txed_samples, NUM_SF); + + printf("closing tx device\n"); + srsran_rf_close(&enb_radio); +} + +int run_test(const char* rx_args, const char* tx_args, bool timed_tx) +{ + int ret = SRSRAN_ERROR; + + // make sure we can receive in slots + if (NUM_SF % 5 != 0) { + fprintf(stderr, "number of subframes must be multiple of 5\n"); + goto exit; + } + + // write to file(s) + enb_tx_function(tx_args, timed_tx); + + // read from file(s) + ue_rx_thread_function((void*)rx_args); + + // channel-wise comparison + for (int c = 0; c < NOF_RX_ANT; c++) { + // subframe-wise compare tx'ed and rx'ed data (stop 3 subframes earlier for timed tx) + for (uint32_t i = 0; i < NUM_SF - (timed_tx ? 3 : 0); ++i) { + uint32_t sf_offet = 0; + if (timed_tx && i >= 1) { + // for timed transmission, the enb inserts 3 zero subframes after the first untimed tx + sf_offet = (TX_OFFSET_MS - 1) * SF_LEN; + } + +#if PRINT_SAMPLES + // print first 10 samples for each SF + printf("enb_tx_buffer sf%d:\n", i); + srsran_vec_fprint_c(stdout, &enb_tx_buffer[c][i * SF_LEN], 10); + printf("ue_rx_buffer sf%d:\n", i); + srsran_vec_fprint_c(stdout, &ue_rx_buffer[c][sf_offet + i * SF_LEN], 10); +#endif + +#if COMPARE_BITS + int d = memcmp(&ue_rx_buffer[sf_offet + i * SF_LEN], &enb_tx_buffer[i * SF_LEN], SF_LEN); + if (d) { + d = d > 0 ? d : -d; + fprintf(stderr, "data mismatch in subframe %d, sample %d\n", i, d); + printf("enb_tx_buffer sf%d:\n", i); + srsran_vec_fprint_c(stdout, &enb_tx_buffer[i * SF_LEN + d], 10); + printf("ue_rx_buffer sf%d:\n", i); + srsran_vec_fprint_c(stdout, &ue_rx_buffer[sf_offet + i * SF_LEN + d], 10); + goto exit; + } +#else + srsran_vec_sub_ccc(&ue_rx_buffer[c][sf_offet + i * SF_LEN], + &enb_tx_buffer[c][i * SF_LEN], + &ue_rx_buffer[c][sf_offet + i * SF_LEN], + SF_LEN); + uint32_t max_ix = srsran_vec_max_abs_ci(&ue_rx_buffer[c][sf_offet + i * SF_LEN], SF_LEN); + if (cabsf(ue_rx_buffer[c][sf_offet + i * SF_LEN + max_ix]) > COMPARE_EPSILON) { + fprintf(stderr, "data mismatch in subframe %d\n", i); + goto exit; + } +#endif + } + } + + ret = SRSRAN_SUCCESS; + +exit: + return ret; +} + +int param_test(const char* args_param, const int num_channels) +{ + char rf_args[RF_PARAM_LEN] = {}; + strncpy(rf_args, (char*)args_param, RF_PARAM_LEN - 1); + rf_args[RF_PARAM_LEN - 1] = 0; + + printf("opening tx device with args=%s\n", rf_args); + if (srsran_rf_open_devname(&enb_radio, "file", rf_args, num_channels)) { + fprintf(stderr, "Error opening rf\n"); + return SRSRAN_ERROR; + } + + srsran_rf_close(&enb_radio); + + return SRSRAN_SUCCESS; +} + +void create_file(const char* filename) +{ + FILE* f = fopen(filename, "w"); + fclose(f); +} + +int main() +{ + // create files for testing + create_file("rx_file0"); + create_file("rx_file1"); + create_file("rx_file2"); + create_file("rx_file3"); + + // two RX files + if (param_test("rx_file=rx_file0," + "rx_file1=rx_file1", + 2)) { + fprintf(stderr, "Param test failed!\n"); + return SRSRAN_ERROR; + } + + // multiple RX files, no channel index provided + if (param_test("rx_file=rx_file0," + "rx_file=rx_file1," + "rx_file=rx_file2," + "rx_file=rx_file3," + "base_srate=1.92e6", + 4)) { + fprintf(stderr, "Param test failed!\n"); + return SRSRAN_ERROR; + } + + // one RX, one TX and all generic options + if (param_test("rx_file0=rx_file0," + "tx_file0=tx_file0," + "base_srate=1.92e6", + 1)) { + fprintf(stderr, "Param test failed!\n"); + return SRSRAN_ERROR; + } + + // two RX, two TX + if (param_test("rx_file0=rx_file0," + "rx_file1=rx_file1," + "tx_file0=tx_file0," + "tx_file1=tx_file1", + 2)) { + fprintf(stderr, "Param test failed!\n"); + return SRSRAN_ERROR; + } + +#if NOF_RX_ANT == 1 + // single tx, single rx with continuous transmissions (no decimation, no timed tx) + if (run_test("rx_file=tx_file0,base_srate=1.92e6", "tx_file=tx_file0,base_srate=1.92e6", false) != SRSRAN_SUCCESS) { + fprintf(stderr, "Single tx, single rx test failed (no decimation, no timed tx)!\n"); + return -1; + } +#endif + + // up to 4 trx radios with continous tx (no decimation, no timed tx) + if (run_test("rx_file=tx_file0,rx_file=tx_file1,rx_file=tx_file2,rx_file=tx_file3,base_srate=1.92e6", + "tx_file=tx_file0,tx_file=tx_file1,tx_file=tx_file2,tx_file=tx_file3,base_srate=1.92e6", + false) != SRSRAN_SUCCESS) { + fprintf(stderr, "Multi TRx radio test failed (no decimation, no timed tx)!\n"); + return -1; + } + + // up to 4 trx radios with continous tx (with decimation, no timed tx) + if (run_test("rx_file=tx_file0,rx_file=tx_file1,rx_file=tx_file2,rx_file=tx_file3", + "tx_file=tx_file0,tx_file=tx_file1,tx_file=tx_file2,tx_file=tx_file3", + false) != SRSRAN_SUCCESS) { + fprintf(stderr, "Multi TRx radio test failed (with decimation, no timed tx)!\n"); + return -1; + } + + // up to 4 trx radios with continous tx (with decimation, timed tx) + if (run_test("rx_file=tx_file0,rx_file=tx_file1,rx_file=tx_file2,rx_file=tx_file3", + "tx_file=tx_file0,tx_file=tx_file1,tx_file=tx_file2,tx_file=tx_file3", + true) != SRSRAN_SUCCESS) { + fprintf(stderr, "Two TRx radio test failed (with decimation, timed tx)!\n"); + return -1; + } + + fprintf(stdout, "Test passed!\n"); + + return SRSRAN_SUCCESS; +}