diff --git a/CMakeLists.txt b/CMakeLists.txt index 25878d16d..5a2f59324 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ option(ENABLE_GUI "Enable GUI (using srsGUI)" ON) option(ENABLE_UHD "Enable UHD" ON) option(ENABLE_BLADERF "Enable BladeRF" ON) option(ENABLE_SOAPYSDR "Enable SoapySDR" ON) +option(ENABLE_SKIQ "Enable Sidekiq SDK" ON) option(ENABLE_ZEROMQ "Enable ZeroMQ" ON) option(ENABLE_HARDSIM "Enable support for SIM cards" ON) @@ -211,6 +212,15 @@ if(ENABLE_UHD) endif(UHD_FOUND) endif(ENABLE_UHD) +# SKIQ +if (ENABLE_SKIQ) + find_package(SKIQ) + if(SKIQ_FOUND) + include_directories(${SKIQ_INCLUDE_DIRS}) + link_directories(${SKIQ_LIBRARY_DIRS}) + endif(SKIQ_FOUND) +endif (ENABLE_SKIQ) + # BladeRF if(ENABLE_BLADERF) find_package(bladeRF) @@ -243,12 +253,12 @@ if(ENABLE_TIMEPROF) add_definitions(-DENABLE_TIMEPROF) endif(ENABLE_TIMEPROF) -if(BLADERF_FOUND OR UHD_FOUND OR SOAPYSDR_FOUND OR ZEROMQ_FOUND) +if(BLADERF_FOUND OR UHD_FOUND OR SOAPYSDR_FOUND OR ZEROMQ_FOUND OR SKIQ_FOUND) set(RF_FOUND TRUE CACHE INTERNAL "RF frontend found") -else(BLADERF_FOUND OR UHD_FOUND OR SOAPYSDR_FOUND OR ZEROMQ_FOUND) +else(BLADERF_FOUND OR UHD_FOUND OR SOAPYSDR_FOUND OR ZEROMQ_FOUND OR SKIQ_FOUND) set(RF_FOUND FALSE CACHE INTERNAL "RF frontend found") add_definitions(-DDISABLE_RF) -endif(BLADERF_FOUND OR UHD_FOUND OR SOAPYSDR_FOUND OR ZEROMQ_FOUND) +endif(BLADERF_FOUND OR UHD_FOUND OR SOAPYSDR_FOUND OR ZEROMQ_FOUND OR SKIQ_FOUND) # Boost if(BUILD_STATIC) diff --git a/cmake/modules/FindSKIQ.cmake b/cmake/modules/FindSKIQ.cmake new file mode 100644 index 000000000..c5310e1ba --- /dev/null +++ b/cmake/modules/FindSKIQ.cmake @@ -0,0 +1,58 @@ +INCLUDE(FindPkgConfig) +#PKG_CHECK_MODULES(SKIQ SKIQ) +IF(NOT SKIQ_FOUND) + +FIND_PATH( + SKIQ_INCLUDE_DIRS + NAMES sidekiq_api.h + HINTS $ENV{SKIQ_DIR}/inc + $ENV{SKIQ_DIR}/sidekiq_core/inc + PATHS /usr/local/include + /usr/include +) + +FIND_LIBRARY( + SKIQ_LIBRARY + NAMES sidekiq__x86_64.gcc + HINTS $ENV{SKIQ_DIR}/lib + PATHS /usr/local/lib + /usr/lib + /usr/lib/x86_64-linux-gnu + /usr/local/lib64 + /usr/local/lib32 +) + +FIND_LIBRARY( + SKIQ_LIBRARY_GLIB + NAMES libglib-2.0.a + HINTS $ENV{SKIQ_DIR}/lib/support/x86_64.gcc/usr/lib/epiq + PATHS /usr/local/lib + /usr/lib + /usr/lib/epiq + /usr/lib/x86_64-linux-gnu + /usr/local/lib64 + /usr/local/lib32 +) + +FIND_LIBRARY( + SKIQ_LIBRARY_USB + NAMES libusb-1.0.a + HINTS $ENV{SKIQ_DIR}/lib/support/x86_64.gcc/usr/lib/epiq + PATHS /usr/local/lib + /usr/lib + /usr/lib/epiq + /usr/lib/x86_64-linux-gnu + /usr/local/lib64 + /usr/local/lib32 +) + +set(SKIQ_LIBRARIES ${SKIQ_LIBRARY} ${SKIQ_LIBRARY_GLIB} ${SKIQ_LIBRARY_USB}) + +message(STATUS "SKIQ LIBRARIES " ${SKIQ_LIBRARIES}) +message(STATUS "SKIQ INCLUDE DIRS " ${SKIQ_INCLUDE_DIRS}) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(SKIQ DEFAULT_MSG SKIQ_LIBRARIES SKIQ_INCLUDE_DIRS) +MARK_AS_ADVANCED(SKIQ_LIBRARIES SKIQ_INCLUDE_DIRS) + +ENDIF(NOT SKIQ_FOUND) diff --git a/lib/src/phy/rf/CMakeLists.txt b/lib/src/phy/rf/CMakeLists.txt index 6155dd877..5176719e8 100644 --- a/lib/src/phy/rf/CMakeLists.txt +++ b/lib/src/phy/rf/CMakeLists.txt @@ -47,6 +47,13 @@ if(RF_FOUND) list(APPEND SOURCES_RF rf_soapy_imp.c) endif (SOAPYSDR_FOUND AND ENABLE_SOAPYSDR) + if(SKIQ_FOUND) + add_executable(skiq_pps_test skiq_pps_test.c) + target_link_libraries(skiq_pps_test ${SKIQ_LIBRARIES} rt pthread m) + add_definitions(-DENABLE_SIDEKIQ) + list(APPEND SOURCES_RF rf_skiq_imp.c rf_skiq_imp_card.c rf_skiq_imp_port.c) + endif(SKIQ_FOUND) + if (ZEROMQ_FOUND) add_definitions(-DENABLE_ZEROMQ) list(APPEND SOURCES_RF rf_zmq_imp.c rf_zmq_imp_tx.c rf_zmq_imp_rx.c) @@ -68,6 +75,10 @@ if(RF_FOUND) target_link_libraries(srsran_rf ${SOAPYSDR_LIBRARIES}) endif (SOAPYSDR_FOUND AND ENABLE_SOAPYSDR) + if(SKIQ_FOUND) + target_link_libraries(srsran_rf ${SKIQ_LIBRARIES} rt) + endif(SKIQ_FOUND) + if (ZEROMQ_FOUND) target_link_libraries(srsran_rf ${ZEROMQ_LIBRARIES}) add_executable(rf_zmq_test rf_zmq_test.c) diff --git a/lib/src/phy/rf/rf_dev.h b/lib/src/phy/rf/rf_dev.h index 20a818149..43cdd1176 100644 --- a/lib/src/phy/rf/rf_dev.h +++ b/lib/src/phy/rf/rf_dev.h @@ -216,6 +216,41 @@ static rf_dev_t dev_zmq = {"zmq", .srsran_rf_send_timed_multi = rf_zmq_send_timed_multi}; #endif +/* Define implementation for bladeRF */ +#ifdef ENABLE_SIDEKIQ + +#include "rf_skiq_imp.h" + +static rf_dev_t dev_skiq = {.name = "Sidekiq", + .srsran_rf_devname = rf_skiq_devname, + .srsran_rf_start_rx_stream = rf_skiq_start_rx_stream, + .srsran_rf_stop_rx_stream = rf_skiq_stop_rx_stream, + .srsran_rf_flush_buffer = rf_skiq_flush_buffer, + .srsran_rf_has_rssi = rf_skiq_has_rssi, + .srsran_rf_get_rssi = rf_skiq_get_rssi, + .srsran_rf_suppress_stdout = rf_skiq_suppress_stdout, + .srsran_rf_register_error_handler = rf_skiq_register_error_handler, + .srsran_rf_open = rf_skiq_open, + .srsran_rf_open_multi = rf_skiq_open_multi, + .srsran_rf_close = rf_skiq_close, + .srsran_rf_set_rx_srate = rf_skiq_set_rx_srate, + .srsran_rf_set_tx_srate = rf_skiq_set_tx_srate, + .srsran_rf_set_rx_gain = rf_skiq_set_rx_gain, + .srsran_rf_set_tx_gain = rf_skiq_set_tx_gain, + .srsran_rf_set_tx_gain_ch = rf_skiq_set_tx_gain_ch, + .srsran_rf_set_rx_gain_ch = rf_skiq_set_rx_gain_ch, + .srsran_rf_get_rx_gain = rf_skiq_get_rx_gain, + .srsran_rf_get_tx_gain = rf_skiq_get_tx_gain, + .srsran_rf_get_info = rf_skiq_get_info, + .srsran_rf_set_rx_freq = rf_skiq_set_rx_freq, + .srsran_rf_set_tx_freq = rf_skiq_set_tx_freq, + .srsran_rf_get_time = rf_skiq_get_time, + .srsran_rf_recv_with_time = rf_skiq_recv_with_time, + .srsran_rf_recv_with_time_multi = rf_skiq_recv_with_time_multi, + .srsran_rf_send_timed = rf_skiq_send_timed, + .srsran_rf_send_timed_multi = rf_skiq_send_timed_multi}; +#endif + //#define ENABLE_DUMMY_DEV #ifdef ENABLE_DUMMY_DEV @@ -246,6 +281,9 @@ static rf_dev_t* available_devices[] = { #ifdef ENABLE_ZEROMQ &dev_zmq, #endif +#ifdef ENABLE_SIDEKIQ + &dev_skiq, +#endif #ifdef ENABLE_DUMMY_DEV &dev_dummy, #endif diff --git a/lib/src/phy/rf/rf_skiq_imp.c b/lib/src/phy/rf/rf_skiq_imp.c new file mode 100644 index 000000000..3d502f100 --- /dev/null +++ b/lib/src/phy/rf/rf_skiq_imp.c @@ -0,0 +1,946 @@ +/** + * + * \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 + +#include + +#include "rf_helper.h" +#include "rf_skiq_imp.h" +#include "rf_skiq_imp_card.h" + +/** + * According the document Sidekiq API 4.13.0 @ref AD9361TimestampSlip: + * Functions that will affect the timestamp: + * • skiq_write_rx_LO_freq() + * • skiq_write_rx_sample_rate_and_bandwidth() + * • skiq_write_tx_LO_freq() + * • skiq_run_tx_quadcal() + * • skiq_write_rx_freq_tune_mode() + * • skiq_write_tx_freq_tune_mode() + * Functions that will be affected by the timestamp slip: + * • skiq_read_last_1pps_timestamp() + * • skiq_receive() + * • skiq_transmit() + * • skiq_read_curr_rx_timestamp() + * • skiq_read_curr_tx_timestamp() + * + * The functions mentioned on the first group above can be divided in two groups. The first group are the ones that + * require restart the tx/rx streams of cards: + * • skiq_write_rx_sample_rate_and_bandwidth() + * + * The module assumes: + * - Tx and Rx sampling rates are equal + * - Tx/Rx shall be stopped during the configuration + * - skiq_stop_rx_streaming_multi_immediate can be called while skiq_receive is being executed + * - skiq_receive shall not be called while skiq_stop_rx_streaming_multi_immediate + * + * In order to update the sampling rate, the RF module shall: + * - Stop all cards Rx streams + * - Stop all cards Tx streams + * - Update Tx/Rx sample rates + * - Start Rx stream + * - enable Tx stream on the next transmission + * + * The second group do not require restarting the tx/rx streams. Indeed, they only affect to a single card and there is + * no interest on stalling the rest of cards stream. Because of this, the module shall suspend the affected card. + * • skiq_write_rx_LO_freq() + * • skiq_write_tx_LO_freq() + * + * The module assumes: + * - The Tx/Rx LO frequency is changed for a single card + * - The Tx/Rx is stalled only in selected card + * - The rest of cards shall keep operating without stalling their streams + * + * In order to update the Tx/Rx LO frequencies, the RF module shall: + * - Suspend the Tx/Rx streams of the card: + * - If receive port ring-buffer has samples, the module shall keep reading from ringbuffer; + * - Otherwise, the module shall not read from ring-buffer and write zeros in the receive buffer + * - Set the Tx/Rx LO frequency + * - Resume the reception + * + */ + +uint32_t rf_skiq_logging_level = SKIQ_LOG_INFO; + +typedef struct { + uint32_t nof_cards; + uint32_t nof_ports; + + rf_skiq_card_t cards[SKIQ_MAX_NUM_CARDS]; + + float cur_tx_gain; + + srsran_rf_info_t info; + + uint64_t next_tstamp; + + double current_srate_hz; + cf_t dummy_buffer[RF_SKIQ_DUMMY_BUFFER_SIZE]; + + pthread_mutex_t mutex_rx; ///< Makes sure receive function and sampling rate setter are not running simultaneously + +} rf_skiq_handler_t; + +void rf_skiq_suppress_stdout(void* h) +{ + SKIQ_RF_INFO("Suppressing stdout... lowering logging level to warning\n"); + // rf_skiq_logging_level = SKIQ_LOG_WARNING; +} + +static bool rf_skiq_is_streaming(rf_skiq_handler_t* h) +{ + // If a single card is streaming, return true + for (uint32_t i = 0; i < h->nof_cards; i++) { + if (rf_skiq_card_is_streaming(&h->cards[i])) { + return true; + } + } + + return false; +} + +bool rf_skiq_check_synch(rf_skiq_handler_t* h) +{ + // Get first card system timestamp + int64_t ts0_sys = (int64_t)rf_skiq_card_read_sys_timestamp(&h->cards[0]); + int64_t ts0_rf = (int64_t)rf_skiq_card_read_rf_timestamp(&h->cards[0]); + SKIQ_RF_INFO(" ... Card 0 TS(sys/rf)=%ld/%ld\n", ts0_sys, ts0_rf); + + bool pass = true; + // Compare all the other card timestamps + for (uint32_t i = 1; i < h->nof_cards; i++) { + int64_t ts2_sys = (int64_t)rf_skiq_card_read_sys_timestamp(&h->cards[i]); + int64_t ts2_rf = (int64_t)rf_skiq_card_read_rf_timestamp(&h->cards[i]); + + // Use current sampling rate + double srate = h->current_srate_hz; + + // If the current srate was not set (zero, nan or Inf), read it back from the first card + if (!isnormal(srate)) { + uint32_t srate_int = 0; + if (skiq_read_rx_sample_rate(0, skiq_rx_hdl_A1, &srate_int, &srate)) { + ERROR("Error reading sampling rate\n"); + } + } + + // Make sure all cards system and RF timestamps are inside maximum allowed error window + bool card_pass = labs(ts2_sys - ts0_sys) < SKIQ_CARD_SYNC_MAX_ERROR; + card_pass = card_pass && labs(ts2_rf - ts0_rf) < (int64_t)(srate / 2); + + // It is enough that a card does not pass to fail the check + pass = pass && card_pass; + + SKIQ_RF_INFO(" ... Card %d TS(sys/rf)=(%ld/%ld) (%.4f/%.4f). %s\n", + i, + ts2_sys, + ts2_rf, + (double)labs(ts2_sys - ts0_sys) / (double)SKIQ_SYS_TIMESTAMP_FREQ, + (double)labs(ts2_rf - ts0_rf) / srate, + card_pass ? "Ok" : "KO"); + } + + return pass; +} + +/** + * Synchronizes single and multiple cards using the PPS signal. This function helper shall be used once at + * initialization. + * + * @param h SKIQ driver handler + * @return SRSRAN_SUCCESS if it is possible to synchronize boards, SRSRAN_ERROR otherwise + */ +static int rf_skiq_synch_cards(rf_skiq_handler_t* h) +{ + bool do_1pps = h->nof_cards > 1; //< PPS is required when more than one card is used + uint32_t trials = 0; //< Count PPS synchronization check trials + + // Try synchronizing timestamps of all cards up to 10 trials + do { + SKIQ_RF_INFO("Resetting system timestamp trial %d/%d\n", trials + 1, SKIQ_CARD_SYNCH_MAX_TRIALS); + + // Reset timestamp in next PPS + for (int i = 0; i < h->nof_cards; i++) { + // Sets the timestamps to the last Rx known time. + if (rf_skiq_card_update_timestamp( + &h->cards[i], do_1pps, h->cards->rx_ports->rb_tstamp_rem + h->current_srate_hz) != SRSRAN_SUCCESS) { + return SRSRAN_ERROR; + } + } + + // It could be that one or more cards PPS reset was issued just after a PPS signal, so only if there is PPS + // (multiple cards), verifies that all cards are synchronised on the same PPS + if (!do_1pps) { + return SRSRAN_SUCCESS; + } + + // Wait for a second to pass + SKIQ_RF_INFO(" ... Waiting PPS to pass ...\n"); + sleep(1); + SKIQ_RF_INFO(" ... Checking:\n"); + + // Successful synchronization across boards! + if (rf_skiq_check_synch(h)) { + return SRSRAN_SUCCESS; + } + + // Increment trial count + trials++; + } while (trials < SKIQ_CARD_SYNCH_MAX_TRIALS); + + // All trials have been consumed without a Successful synchronization + ERROR("Error card synchronization failed\n"); + return SRSRAN_ERROR; +} + +void rf_skiq_register_error_handler(void* h_, srsran_rf_error_handler_t error_handler, void* arg) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + + SKIQ_RF_INFO("Registering error handler...\n"); + + // Set error handler for each card + for (uint32_t i = 0; i < h->nof_cards; i++) { + rf_skiq_card_set_error_handler(&h->cards[i], error_handler, arg); + } +} + +const char* rf_skiq_devname(void* h) +{ + return "Sidekiq"; +} + +int rf_skiq_start_rx_stream(void* h_, bool now) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + + rf_skiq_synch_cards(h); + + // Get current system timestamp, assume all cards are synchronized + uint64_t ts = rf_skiq_card_read_sys_timestamp(&h->cards[0]); + + // Advance a 10th of a second (100ms) + ts += SKIQ_SYS_TIMESTAMP_FREQ / 10; + + // Start streams for each card at the indicated timestamp... + for (uint32_t i = 0; i < h->nof_cards; i++) { + if (rf_skiq_card_start_rx_streaming(&h->cards[i], ts)) { + return SRSRAN_ERROR; + } + } + + return SRSRAN_SUCCESS; +} + +int rf_skiq_stop_rx_stream(void* h_) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + + for (int i = 0; i < h->nof_cards; i++) { + rf_skiq_card_stop_rx_streaming(&h->cards[i]); + } + + return SRSRAN_SUCCESS; +} + +static int rf_skiq_send_end_of_burst(void* h_) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + + for (int i = 0; i < h->nof_cards; i++) { + rf_skiq_card_end_of_burst(&h->cards[i]); + } + + return SRSRAN_SUCCESS; +} + +void rf_skiq_flush_buffer(void* h) +{ + SKIQ_RF_INFO("Flushing buffers...\n"); + + int n; + void* data[SKIQ_MAX_CHANNELS] = {}; + do { + n = rf_skiq_recv_with_time_multi(h, data, 1024, 0, NULL, NULL); + } while (n > 0); +} + +bool rf_skiq_has_rssi(void* h) +{ + return false; +} + +float rf_skiq_get_rssi(void* h) +{ + return 0.0f; +} + +int rf_skiq_open(char* args, void** h) +{ + return rf_skiq_open_multi(args, h, 1); +} + +void rf_skiq_log_msg(int32_t priority, const char* message) +{ + if (priority <= rf_skiq_logging_level) { + printf("%s", message); + } +} + +int rf_skiq_open_multi(char* args, void** h_, uint32_t nof_channels) +{ + // Check number of antennas bounds + if (nof_channels < SKIQ_MIN_CHANNELS || nof_channels > SKIQ_MAX_CHANNELS) { + ERROR("Number of channels (%d) not supported (%d-%d)\n", nof_channels, SKIQ_MIN_CHANNELS, SKIQ_MAX_CHANNELS); + return SRSRAN_ERROR_OUT_OF_BOUNDS; + } + + rf_skiq_handler_t* h = (rf_skiq_handler_t*)calloc(1, sizeof(rf_skiq_handler_t)); + if (!h) { + return SRSRAN_ERROR; + } + *h_ = h; + + // Parse main parameters + parse_uint32(args, "nof_cards", 0, &h->nof_cards); + parse_uint32(args, "nof_ports", 0, &h->nof_ports); + + char log_level[RF_PARAM_LEN] = "info"; + parse_string(args, "log_level", 0, log_level); + if (strcmp(log_level, "info") == 0) { + rf_skiq_logging_level = SKIQ_LOG_INFO; + } else if (strcmp(log_level, "debug") == 0) { + rf_skiq_logging_level = SKIQ_LOG_DEBUG; + } else if (strcmp(log_level, "warn") == 0) { + rf_skiq_logging_level = SKIQ_LOG_WARNING; + } else if (strcmp(log_level, "error") == 0) { + rf_skiq_logging_level = SKIQ_LOG_ERROR; + } else { + ERROR("Error log_level %s is undefined. Options: debug, info, warn and error\n", log_level); + return SRSRAN_ERROR; + } + + // Register Logger + skiq_register_logging(&rf_skiq_log_msg); + + // Get available cards + uint8_t nof_available_cards = 0; + uint8_t available_cards[SKIQ_MAX_NUM_CARDS] = {}; + if (skiq_get_cards(skiq_xport_type_auto, &nof_available_cards, available_cards)) { + ERROR("Getting available cards\n"); + return SRSRAN_ERROR; + } + + // + if (h->nof_cards == 0 && h->nof_ports == 0) { + if (nof_channels <= (uint32_t)nof_available_cards) { + // One channel per card + h->nof_cards = nof_channels; + h->nof_ports = 1; + } else if (nof_channels <= RF_SKIQ_MAX_PORTS_CARD) { + // One channel per port + h->nof_cards = 1; + h->nof_ports = nof_channels; + } else if (nof_channels % RF_SKIQ_MAX_PORTS_CARD == 0) { + // use all ports + h->nof_cards = nof_channels / RF_SKIQ_MAX_PORTS_CARD; + h->nof_ports = RF_SKIQ_MAX_PORTS_CARD; + } else if (nof_channels % nof_available_cards == 0) { + // use all cards + h->nof_cards = nof_available_cards; + h->nof_ports = nof_channels / nof_available_cards; + } else { + ERROR("Error deducing the number of cards and ports"); + } + } else if (h->nof_ports == 0 && nof_channels % h->nof_cards == 0) { + h->nof_ports = nof_channels / h->nof_cards; + } else if (h->nof_cards == 0 && nof_channels % h->nof_ports == 0) { + h->nof_cards = nof_channels / h->nof_ports; + } + + if (h->nof_cards == 0 || h->nof_cards > nof_available_cards) { + ERROR("Error invalid number of cards %d, available %d\n", h->nof_cards, nof_available_cards); + return SRSRAN_ERROR_OUT_OF_BOUNDS; + } + + if (h->nof_ports == 0 || h->nof_ports > RF_SKIQ_MAX_PORTS_CARD) { + ERROR("Error invalid number of cards %d, available %d\n", h->nof_cards, nof_available_cards); + return SRSRAN_ERROR_OUT_OF_BOUNDS; + } + + // Create default port options + rf_skiq_port_opts_t port_opts = {}; + port_opts.tx_rb_size = 2048; + port_opts.rx_rb_size = 2048; + port_opts.chan_mode = (h->nof_ports > 1) ? skiq_chan_mode_dual : skiq_chan_mode_single; + port_opts.stream_mode = skiq_rx_stream_mode_balanced; + + // Parse other options + parse_uint32(args, "tx_rb_size", 0, &port_opts.tx_rb_size); + parse_uint32(args, "rx_rb_size", 0, &port_opts.rx_rb_size); + parse_string(args, "mode", 0, port_opts.stream_mode_str); + + if (strlen(port_opts.stream_mode_str) > 0) { + if (strcmp(port_opts.stream_mode_str, "low_latency") == 0) { + port_opts.stream_mode = skiq_rx_stream_mode_low_latency; + } else if (strcmp(port_opts.stream_mode_str, "balanced") == 0) { + port_opts.stream_mode = skiq_rx_stream_mode_balanced; + } else if (strcmp(port_opts.stream_mode_str, "high_tput") == 0) { + port_opts.stream_mode = skiq_rx_stream_mode_high_tput; + } else { + ERROR("Invalid mode: %s; Valid modes are: low_latency, balanced, high_tput\n", port_opts.stream_mode_str); + return SRSRAN_ERROR; + } + } + + SKIQ_RF_INFO("Opening %d SKIQ cards with %d ports...\n", h->nof_cards, h->nof_ports); + + if (pthread_mutex_init(&h->mutex_rx, NULL)) { + ERROR("Error initialising mutex\n"); + return SRSRAN_ERROR; + } + + // Initialise driver + if (skiq_init(skiq_xport_type_auto, skiq_xport_init_level_full, available_cards, h->nof_cards)) { + ERROR("Unable to initialise libsidekiq driver\n"); + return SRSRAN_ERROR; + } + + // Initialise each card + for (uint32_t i = 0; i < h->nof_cards; i++) { + if (rf_skiq_card_init(&h->cards[i], available_cards[i], h->nof_ports, &port_opts)) { + return SRSRAN_ERROR; + } + } + + // Parse default frequencies + for (uint32_t i = 0, ch = 0; i < h->nof_cards; i++) { + for (uint32_t j = 0; j < h->nof_ports; j++, ch++) { + double tx_freq = 0.0; + parse_double(args, "tx_freq", ch, &tx_freq); + + if (isnormal(tx_freq)) { + rf_skiq_set_tx_freq(h, ch, tx_freq); + } + double rx_freq = 0.0; + parse_double(args, "rx_freq", ch, &rx_freq); + + if (isnormal(rx_freq)) { + rf_skiq_set_rx_freq(h, ch, rx_freq); + } + } + } + + // Set a default gain + rf_skiq_set_rx_gain(h, SKIQ_RX_GAIN_DEFAULT_dB); + + // Parse default sample rate + double srate_hz = 0.0; + parse_double(args, "srate", 0, &srate_hz); + srate_hz = isnormal(srate_hz) ? srate_hz : SKIQ_DEFAULT_SAMPLING_RATE_HZ; + + // Set a default sampling rate, default can be too low + rf_skiq_set_tx_srate(h, srate_hz); + rf_skiq_set_rx_srate(h, srate_hz); + + return SRSRAN_SUCCESS; +} + +int rf_skiq_close(void* h_) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + + SKIQ_RF_INFO("Closing...\n"); + + // Ensure Tx/Rx streaming is stopped + rf_skiq_send_end_of_burst(h); + rf_skiq_stop_rx_stream(h); + + // Free all open cards + for (int i = 0; i < h->nof_cards; i++) { + rf_skiq_card_close(&h->cards[i]); + } + + // Close sidekiq SDK + skiq_exit(); + + pthread_mutex_destroy(&h->mutex_rx); + + // Deallocate object memory + if (h != NULL) { + free(h); + } + + return SRSRAN_SUCCESS; +} + +static double rf_skiq_set_srate_hz(rf_skiq_handler_t* h, double srate_hz) +{ + // If the sampling rate is not modified dont bother + if (h->current_srate_hz == srate_hz) { + return srate_hz; + } + SKIQ_RF_INFO("Setting sampling rate to %.2f MHz ...\n", srate_hz / 1e6); + + // Save streaming state + bool is_streaming = rf_skiq_is_streaming(h); + + // Stop streaming + SKIQ_RF_INFO(" ... Stop Tx/Rx streaming\n"); + rf_skiq_send_end_of_burst(h); + rf_skiq_stop_rx_stream(h); + + // Set sampling + SKIQ_RF_INFO(" ... Setting sampling rates to %.2f MHz\n", srate_hz / 1e6); + pthread_mutex_lock(&h->mutex_rx); + for (uint32_t i = 0; i < h->nof_cards; i++) { + rf_skiq_card_set_srate_hz(&h->cards[i], (uint32_t)srate_hz); + } + pthread_mutex_unlock(&h->mutex_rx); + + // Start streaming if it was started + if (is_streaming) { + SKIQ_RF_INFO(" ... Start Rx streaming\n"); + rf_skiq_start_rx_stream(h, true); + } + + // Update current sampling rate + h->current_srate_hz = srate_hz; + SKIQ_RF_INFO(" ... Done!\n"); + + return srate_hz; +} + +double rf_skiq_set_rx_srate(void* h, double sample_rate) +{ + return rf_skiq_set_srate_hz((rf_skiq_handler_t*)h, sample_rate); +} + +double rf_skiq_set_tx_srate(void* h, double sample_rate) +{ + return rf_skiq_set_srate_hz((rf_skiq_handler_t*)h, sample_rate); +} + +int rf_skiq_set_rx_gain(void* h_, double rx_gain) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + + for (uint32_t i = 0; i < h->nof_cards; i++) { + rf_skiq_card_set_rx_gain_db(&h->cards[i], h->nof_ports, rx_gain); + } + + return SRSRAN_SUCCESS; +} + +int rf_skiq_set_tx_gain(void* h_, double tx_gain) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + + for (uint32_t i = 0; i < h->nof_cards; i++) { + h->cur_tx_gain = rf_skiq_card_set_tx_gain_db(&h->cards[i], h->nof_ports, tx_gain); + } + + return SRSRAN_SUCCESS; +} + +int rf_skiq_set_tx_gain_ch(void* h_, uint32_t ch, double tx_gain) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + uint32_t card_idx = ch / h->nof_ports; + uint32_t port_idx = ch % h->nof_ports; + + if (card_idx >= h->nof_cards || port_idx >= h->nof_ports) { + return SRSRAN_ERROR_OUT_OF_BOUNDS; + } + + tx_gain = rf_skiq_card_set_tx_gain_db(&h->cards[card_idx], port_idx, tx_gain); + + if (ch == 0) { + h->cur_tx_gain = tx_gain; + } + + return SRSRAN_SUCCESS; +} + +int rf_skiq_set_rx_gain_ch(void* h_, uint32_t ch, double rx_gain) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + uint32_t card_idx = ch / h->nof_ports; + uint32_t port_idx = ch % h->nof_ports; + + if (card_idx >= h->nof_cards || port_idx >= h->nof_ports) { + return SRSRAN_ERROR_OUT_OF_BOUNDS; + } + + rx_gain = rf_skiq_card_set_rx_gain_db(&h->cards[card_idx], port_idx, rx_gain); + + if (ch == 0) { + h->cur_tx_gain = rx_gain; + } + + return SRSRAN_SUCCESS; +} + +double rf_skiq_get_rx_gain(void* h_) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + + return h->cards[0].cur_rx_gain_db; +} + +double rf_skiq_get_tx_gain(void* h_) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + return h->cur_tx_gain; +} + +srsran_rf_info_t* rf_skiq_get_info(void* h_) +{ + srsran_rf_info_t* ret = NULL; + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + + if (h != NULL) { + ret = &h->info; + + rf_skiq_card_update_gain_table(&h->cards[0]); + + ret->min_tx_gain = 0.25 * (double)h->cards[0].param.tx_param->atten_quarter_db_max; + ret->max_tx_gain = 0.25 * (double)h->cards[0].param.tx_param->atten_quarter_db_min; + ret->min_rx_gain = h->cards[0].rx_gain_table_db[h->cards[0].param.rx_param[0].gain_index_min]; + ret->max_rx_gain = h->cards[0].rx_gain_table_db[h->cards[0].param.rx_param[0].gain_index_max]; + } + + return ret; +} + +double rf_skiq_set_rx_freq(void* h_, uint32_t ch, double freq) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + + uint32_t card_idx = ch / h->nof_ports; + uint32_t port_idx = ch % h->nof_ports; + +#pragma message "TODO: The Rx stream needs to stop, RF timestamp shall be aligned with other cards and start again" + + if (card_idx < h->nof_cards && port_idx < h->nof_ports) { + return rf_skiq_card_set_rx_freq_hz(&h->cards[card_idx], port_idx, freq); + } + + return freq; +} + +double rf_skiq_set_tx_freq(void* h_, uint32_t ch, double freq) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + + uint32_t card_idx = ch / h->nof_ports; + uint32_t port_idx = ch % h->nof_ports; + +#pragma message "TODO: The Rx stream needs to stop, RF timestamp shall be aligned with other cards and start again" + + if (card_idx < h->nof_cards && port_idx < h->nof_ports) { + return rf_skiq_card_set_tx_freq_hz(&h->cards[card_idx], port_idx, freq); + } + + return freq; +} + +void tstamp_to_time(rf_skiq_handler_t* h, uint64_t tstamp, time_t* secs, double* frac_secs) +{ + uint64_t srate_hz = (uint64_t)h->current_srate_hz; + + if (srate_hz == 0) { + ERROR("Warning: Sampling rate has not been set yet.\n"); + srate_hz = UINT64_MAX; + } + + if (secs) { + *secs = (time_t)tstamp / srate_hz; + } + if (frac_secs) { + uint64_t rem = tstamp % srate_hz; + *frac_secs = (double)rem / h->current_srate_hz; + } +} + +uint64_t time_to_tstamp(rf_skiq_handler_t* h, time_t secs, double frac_secs) +{ + return secs * h->current_srate_hz + frac_secs * h->current_srate_hz; +} + +void rf_skiq_get_time(void* h_, time_t* secs, double* frac_secs) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + uint64_t tstamp = rf_skiq_card_read_rf_timestamp(&h->cards[0]); + tstamp_to_time(h, tstamp, secs, frac_secs); +} + +static int +rf_skiq_discard_rx_samples(rf_skiq_handler_t* h, uint32_t card, uint32_t port, uint32_t nsamples, uint64_t* ts_start) +{ + *ts_start = 0; + while (nsamples > 0) { + uint64_t ts = 0; + + // Receive in dummy buffer + int32_t n = rf_skiq_card_receive( + &h->cards[card], port, h->dummy_buffer, SRSRAN_MIN(nsamples, RF_SKIQ_DUMMY_BUFFER_SIZE), &ts); + + // Check for error + if (n < 0) { + ERROR("An error occurred discarding %d Rx samples for channel %d:%d\n", nsamples, card, port); + return n; + } + + // Save first timestamp + if (*ts_start == 0) { + *ts_start = ts; + } + + // Decrement pending samples + nsamples -= n; + } + + return nsamples; +} + +static int rf_skiq_synch_rx_ports(rf_skiq_handler_t* h) +{ + int64_t tstamp_min = INT64_MAX; + int64_t tstamp_max = 0; + + // no need to synchronize + if (h->nof_cards * h->nof_ports < 2) { + return SRSRAN_SUCCESS; + } + + // Find minimum and maximum next timestamps + for (uint32_t card = 0; card < h->nof_cards; card++) { + // Iterate for all ports + for (uint32_t port = 0; port < h->nof_ports; port++) { + int64_t ts = (int64_t)rf_skiq_card_get_rx_timestamp(&h->cards[card], port); + + // If the card is not streaming or suspended will return TS 0; so skip + if (ts == 0UL) { + continue; + } + + // Is minimum? + tstamp_min = SRSRAN_MIN(tstamp_min, ts); + + // Is maximum? + tstamp_max = SRSRAN_MAX(tstamp_max, ts); + } + } + + // Check if any synchronization is required + if (tstamp_max == tstamp_min) { + return SRSRAN_SUCCESS; + } + + // Align all channels to the maximum timestamp + for (uint32_t card = 0; card < h->nof_cards; card++) { + // Iterate for all ports + for (uint32_t port = 0; port < h->nof_ports; port++) { + uint64_t ts = rf_skiq_card_get_rx_timestamp(&h->cards[card], port); + + // If the card is not streaming or suspended will return TS 0; so skip + if (ts == 0UL) { + continue; + } + + // Calculate number of samples + int nsamples = (int)(tstamp_max - (int64_t)ts); + + // Skip port if negative or zero (possible if stream is enabled during this time) + if (nsamples <= 0) { + continue; + } + + // Too many samples, sign of some extreme error + if (nsamples > SKIQ_PORT_SYNC_MAX_GAP) { + ERROR("too many samples to align (%d) for channel %d:%d\n", nsamples, card, port); + return SRSRAN_ERROR; + } + + ts = 0; + if (rf_skiq_discard_rx_samples(h, card, port, nsamples, &ts) < SRSRAN_SUCCESS) { + ERROR("Error occurred during Rx streams alignment."); + return SRSRAN_ERROR; + } + } + } + + return SRSRAN_SUCCESS; +} + +int rf_skiq_recv_with_time(void* h, void* data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs) +{ + return rf_skiq_recv_with_time_multi(h, &data, nsamples, blocking, secs, frac_secs); +} + +int rf_skiq_recv_with_time_multi(void* h_, + void** data_, + uint32_t nsamples, + bool blocking, + time_t* secs, + double* frac_secs) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + uint64_t ts_start = 0; + cf_t** data = (cf_t**)data_; + + pthread_mutex_lock(&h->mutex_rx); + + // Perform channel synchronization + rf_skiq_synch_rx_ports(h); + + bool completed = false; + uint32_t count[SKIQ_MAX_CHANNELS] = {}; + + while (!completed) { + // Completion true by default + completed = true; + + // Iterate over cards + for (uint32_t card = 0, chan = 0; card < h->nof_cards; card++) { + // Iterate over ports + for (uint32_t port = 0; port < h->nof_ports; port++, chan++) { + // Calculate number of pending samples + uint32_t pending = nsamples - count[chan]; + + if (pending > 0) { + uint64_t ts = 0; + int n = 0; + + // If data is not provided... + if (data[chan] == NULL) { + // ... discard up to RF_SKIQ_DUMMY_BUFFER_SIZE samples + n = rf_skiq_card_receive( + &h->cards[card], port, h->dummy_buffer, SRSRAN_MIN(pending, RF_SKIQ_DUMMY_BUFFER_SIZE), &ts); + } else { + // ... read base-band + n = rf_skiq_card_receive(&h->cards[card], port, &data[chan][count[chan]], pending, &ts); + } + + // If error is detected, return it + if (n < SRSRAN_SUCCESS) { + pthread_mutex_unlock(&h->mutex_rx); + return n; + } + + // Save first valid timestamp + if (ts_start == 0) { + ts_start = ts; + } + + // Increment count for the channel + count[chan] += n; + + // Lower completed flag if at least a channel has not reach the target + if (count[chan] < nsamples) { + completed = false; + } + } + } + } + } + + pthread_mutex_unlock(&h->mutex_rx); + + // Convert u64 to srsran timestamp + tstamp_to_time(h, ts_start, secs, frac_secs); + + // No error, return number of received samples + return nsamples; +} + +int rf_skiq_send_timed(void* h, + void* data, + int nsamples, + time_t secs, + double frac_secs, + bool has_time_spec, + bool blocking, + bool is_start_of_burst, + bool is_end_of_burst) +{ + void* _data[SRSRAN_MAX_PORTS] = {}; + _data[0] = data; + + return rf_skiq_send_timed_multi( + h, _data, nsamples, secs, frac_secs, has_time_spec, blocking, is_start_of_burst, is_end_of_burst); +} + +int rf_skiq_send_timed_multi(void* h_, + void** data_, + int nsamples, + time_t secs, + double frac_secs, + bool has_time_spec, + bool blocking, + bool is_start_of_burst, + bool is_end_of_burst) +{ + rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_; + cf_t** data = (cf_t**)data_; + + // Force timestamp only if start of burst + if (is_start_of_burst) { + if (has_time_spec) { + h->next_tstamp = time_to_tstamp(h, secs, frac_secs); + } else { + h->next_tstamp = rf_skiq_card_read_rf_timestamp(&h->cards[0]); + h->next_tstamp += (uint64_t)round(h->current_srate_hz / 10); // increment a 10th of a second + } + } + + uint32_t rpm = 0; + bool completed = false; + uint32_t count[SKIQ_MAX_CHANNELS] = {}; + + while (!completed) { + // Completion true by default + completed = true; + + // Iterate all cards... + for (uint32_t card = 0, chan = 0; card < h->nof_cards; card++) { + // Iterate all ports... + for (uint32_t port = 0; port < h->nof_ports; port++, chan++) { + // Calculate number of pending samples + uint32_t pending = nsamples - count[chan]; + + if (pending > 0) { + uint64_t ts = h->next_tstamp + count[chan]; + cf_t* ptr = (data[chan] == NULL) ? NULL : &data[chan][count[chan]]; + int n = rf_skiq_card_send(&h->cards[card], port, ptr, pending, ts); + if (n > SRSRAN_SUCCESS) { + count[chan] += n; + } + + if (count[chan] < nsamples) { + completed = false; + } + } + } + } + } + + // Increment timestamp + h->next_tstamp += nsamples; + + if (is_end_of_burst) { + rf_skiq_send_end_of_burst(h); + } + + return (int)rpm; +} diff --git a/lib/src/phy/rf/rf_skiq_imp.h b/lib/src/phy/rf/rf_skiq_imp.h new file mode 100644 index 000000000..54a33dcbe --- /dev/null +++ b/lib/src/phy/rf/rf_skiq_imp.h @@ -0,0 +1,95 @@ +/** + * + * \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 +#include + +#include "srsran/config.h" +#include "srsran/phy/rf/rf.h" + +SRSRAN_API int rf_skiq_open(char* args, void** handler); + +SRSRAN_API int rf_skiq_open_multi(char* args, void** handler, uint32_t nof_rx_antennas); + +SRSRAN_API const char* rf_skiq_devname(void* h); + +SRSRAN_API int rf_skiq_close(void* h); + +SRSRAN_API int rf_skiq_start_rx_stream(void* h, bool now); + +SRSRAN_API int rf_skiq_start_rx_stream_nsamples(void* h, uint32_t nsamples); + +SRSRAN_API int rf_skiq_stop_rx_stream(void* h); + +SRSRAN_API void rf_skiq_flush_buffer(void* h); + +SRSRAN_API bool rf_skiq_has_rssi(void* h); + +SRSRAN_API float rf_skiq_get_rssi(void* h); + +SRSRAN_API void rf_skiq_set_master_clock_rate(void* h, double rate); + +SRSRAN_API bool rf_skiq_is_master_clock_dynamic(void* h); + +SRSRAN_API double rf_skiq_set_rx_srate(void* h, double freq); + +SRSRAN_API int rf_skiq_set_rx_gain(void* h, double gain); + +SRSRAN_API int rf_skiq_set_rx_gain_ch(void* h_, uint32_t ch, double rx_gain); + +SRSRAN_API double rf_skiq_get_rx_gain(void* h); + +SRSRAN_API double rf_skiq_get_tx_gain(void* h); + +SRSRAN_API srsran_rf_info_t* rf_skiq_get_info(void* h); + +SRSRAN_API void rf_skiq_suppress_stdout(void* h); + +SRSRAN_API void rf_skiq_register_error_handler(void* h, srsran_rf_error_handler_t error_handler, void* arg); + +SRSRAN_API double rf_skiq_set_rx_freq(void* h, uint32_t ch, double freq); + +SRSRAN_API int +rf_skiq_recv_with_time(void* h, void* data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs); + +SRSRAN_API int +rf_skiq_recv_with_time_multi(void* h, void** data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs); + +SRSRAN_API double rf_skiq_set_tx_srate(void* h, double freq); + +SRSRAN_API int rf_skiq_set_tx_gain(void* h, double gain); + +SRSRAN_API int rf_skiq_set_tx_gain_ch(void* h, uint32_t ch, double gain); + +SRSRAN_API double rf_skiq_set_tx_freq(void* h, uint32_t ch, double freq); + +SRSRAN_API void rf_skiq_get_time(void* h, time_t* secs, double* frac_secs); + +SRSRAN_API int rf_skiq_send_timed(void* h, + void* data, + int nsamples, + time_t secs, + double frac_secs, + bool has_time_spec, + bool blocking, + bool is_start_of_burst, + bool is_end_of_burst); + +SRSRAN_API int rf_skiq_send_timed_multi(void* h, + void* data[4], + int nsamples, + time_t secs, + double frac_secs, + bool has_time_spec, + bool blocking, + bool is_start_of_burst, + bool is_end_of_burst); diff --git a/lib/src/phy/rf/rf_skiq_imp_card.c b/lib/src/phy/rf/rf_skiq_imp_card.c new file mode 100644 index 000000000..083b3d5c7 --- /dev/null +++ b/lib/src/phy/rf/rf_skiq_imp_card.c @@ -0,0 +1,578 @@ +/** + * + * \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_skiq_imp_card.h" + +static void* reader_thread(void* arg) +{ + rf_skiq_card_t* q = (rf_skiq_card_t*)arg; + + while (q->state != RF_SKIQ_PORT_STATE_STOP) { + // Wait to leave idle state + if (q->state == RF_SKIQ_PORT_STATE_IDLE) { + SKIQ_RF_INFO("[Rx %d] IDLE\n", q->card); + pthread_mutex_lock(&q->mutex); + while (q->state == RF_SKIQ_PORT_STATE_IDLE) { + pthread_cond_wait(&q->cvar, &q->mutex); + } + pthread_mutex_unlock(&q->mutex); + SKIQ_RF_INFO("[Rx %d] %s\n", q->card, q->state == RF_SKIQ_PORT_STATE_STREAMING ? "STREAMING" : "STOP"); + } + + // Check exit condition + if (q->state == RF_SKIQ_PORT_STATE_STOP) { + break; + } + + skiq_rx_hdl_t curr_rx_hdl = 0; + skiq_rx_block_t* p_rx_block = NULL; + uint32_t len = 0; + skiq_rx_status_t rx_status = skiq_receive(q->card, &curr_rx_hdl, &p_rx_block, &len); + + switch (rx_status) { + case skiq_rx_status_success: + // Check Rx index boundary + if (p_rx_block->rfic_control >= q->param.rx_param->gain_index_min && + p_rx_block->rfic_control <= q->param.rx_param->gain_index_max) { + double new_rx_gain = q->rx_gain_table_db[p_rx_block->rfic_control]; + + // If the Rx index has changed, update gain + if (new_rx_gain != q->cur_rx_gain_db) { + SKIQ_RF_DEBUG("card %d index=%d; gain=%.2f/%.2f dB;\n", + q->card, + p_rx_block->rfic_control, + q->rx_gain_table_db[p_rx_block->rfic_control], + q->cur_rx_gain_db); + q->cur_rx_gain_db = new_rx_gain; + } + } + if (curr_rx_hdl < q->nof_ports && p_rx_block != NULL && len > SKIQ_RX_HEADER_SIZE_IN_BYTES) { + // Convert number of bytes into samples + uint32_t nsamples = len / 4 - SKIQ_RX_HEADER_SIZE_IN_WORDS; + + // Give block to the port + rf_skiq_rx_port_write(&q->rx_ports[curr_rx_hdl], p_rx_block, nsamples); + } else { + ERROR("Card %d received data with corrupted pointers\n", q->card); + } + break; + case skiq_rx_status_no_data: + // Do nothing + break; + case skiq_rx_status_error_generic: + ERROR("Error: Generic error occurred during skiq_receive.\n"); + break; + case skiq_rx_status_error_overrun: + ERROR("Error: overrun error occurred during skiq_receive.\n"); + break; + case skiq_rx_status_error_packet_malformed: + ERROR("Error: packet malformed error occurred during skiq_receive.\n"); + break; + case skiq_rx_status_error_card_not_active: + ERROR("Error: inactive card error occurred during skiq_receive.\n"); + break; + case skiq_rx_status_error_not_streaming: + ERROR("Error: not streaming card error occurred during skiq_receive.\n"); + break; + default: + ERROR("Error: the impossible happened during skiq_receive.\n"); + break; + } + } + + SKIQ_RF_INFO("Exiting reader thread!\n"); + + return NULL; +} + +int rf_skiq_card_update_gain_table(rf_skiq_card_t* q) +{ + for (uint8_t i = q->param.rx_param->gain_index_min; i <= q->param.rx_param->gain_index_max; i++) { + if (skiq_read_rx_cal_offset_by_gain_index(q->card, skiq_rx_hdl_A1, i, &q->rx_gain_table_db[i])) { + ERROR("Reading calibrated Rx gain index %d", i); + return SRSRAN_ERROR; + } + } + return SRSRAN_SUCCESS; +} + +int rf_skiq_card_init(rf_skiq_card_t* q, uint8_t card, uint8_t nof_ports, const rf_skiq_port_opts_t* opts) +{ + q->card = card; + q->nof_ports = nof_ports; + + // Reprogram FPGA to reset all states + if (skiq_prog_fpga_from_flash(card)) { + ERROR("Error programming card %d from flash\n", q->card); + return SRSRAN_ERROR; + } + + // Read card parameters + if (skiq_read_parameters(card, &q->param)) { + ERROR("Reading card %d param", card); + return SRSRAN_ERROR; + } + + // Check number of rx channels + if (q->param.rf_param.num_rx_channels < nof_ports) { + ERROR("Card %d does not support %d Rx channels", card, nof_ports); + return SRSRAN_ERROR; + } + + // Check number of tx channels + if (q->param.rf_param.num_tx_channels < nof_ports) { + ERROR("Card %d does not support %d Tx channels", card, nof_ports); + return SRSRAN_ERROR; + } + + // set a modest rx timeout + if (skiq_set_rx_transfer_timeout(card, 1000)) { + ERROR("Setting Rx transfer timeout"); + return SRSRAN_ERROR; + } + + // do not pack 12bit samples + if (skiq_write_iq_pack_mode(card, false)) { + ERROR("Setting Rx IQ pack mode"); + return SRSRAN_ERROR; + } + + // set the control output bits to include the gain + if (skiq_write_rfic_control_output_config( + card, RFIC_CONTROL_OUTPUT_MODE_GAIN_CONTROL_RXA1, RFIC_CONTROL_OUTPUT_MODE_GAIN_BITS) != 0) { + ERROR("Unable to configure card %d the RF IC control output (A1)", card); + return SRSRAN_ERROR; + } + + // set RX channel mode + if (skiq_write_chan_mode(card, q->mode)) { + ERROR("Setting card %d channel mode", card); + return SRSRAN_ERROR; + } + + // Select Rx streaming mode to low latency if the sampling rate is lower than 5MHz + if (skiq_write_rx_stream_mode(q->card, opts->stream_mode)) { + ERROR("Error setting Rx stream mode\n"); + return SRSRAN_ERROR; + } + + // initialise tx/rx ports + for (uint8_t i = 0; i < nof_ports; i++) { + if (rf_skiq_tx_port_init(&q->tx_ports[i], card, (skiq_tx_hdl_t)i, opts)) { + ERROR("Initiating card %d, Tx port %d", card, i); + return SRSRAN_ERROR; + } + + if (rf_skiq_rx_port_init(&q->rx_ports[i], card, (skiq_rx_hdl_t)i, opts)) { + ERROR("Initiating card %d, Rx port %d", card, i); + return SRSRAN_ERROR; + } + } + + if (pthread_mutex_init(&q->mutex, NULL)) { + ERROR("Initiating mutex"); + return SRSRAN_ERROR; + } + + if (pthread_cond_init(&q->cvar, NULL)) { + ERROR("Initiating cvar"); + return SRSRAN_ERROR; + } + + // Initialise thread parameters + pthread_attr_t attr; + struct sched_param param; + + param.sched_priority = sched_get_priority_max(SCHED_FIFO); + pthread_attr_init(&attr); + if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) { + ERROR("Error not enough privileges to set Scheduling priority\n"); + } + + if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) { + ERROR("Error not enough privileges to set Scheduling priority\n"); + } + + if (pthread_attr_setschedparam(&attr, ¶m)) { + ERROR("Error not enough privileges to set Scheduling priority\n"); + } + + // Launch thread + if (pthread_create(&q->thread, &attr, reader_thread, q)) { + ERROR("Error creating reader thread with attributes (Did you miss sudo?). Trying without attributes.\n"); + return SRSRAN_ERROR; + } + + // Rename thread + char thread_name[32] = {}; + if (snprintf(thread_name, sizeof(thread_name), "SKIQ Rx %d", q->card) > 0) { + pthread_setname_np(q->thread, thread_name); + } + + return SRSRAN_SUCCESS; +} + +void rf_skiq_card_set_error_handler(rf_skiq_card_t* q, srsran_rf_error_handler_t error_handler, void* arg) +{ + for (uint32_t i = 0; i < q->nof_ports; i++) { + rf_skiq_tx_port_set_error_handler(&q->tx_ports[i], error_handler, arg); + rf_skiq_rx_port_set_error_handler(&q->rx_ports[i], error_handler, arg); + } +} + +double rf_skiq_card_set_tx_gain_db(rf_skiq_card_t* q, uint32_t port_idx, double gain_db) +{ + double max_atten_dB = 0.25 * q->param.tx_param->atten_quarter_db_max; + double min_atten_dB = 0.25 * q->param.tx_param->atten_quarter_db_min; + + // Calculate attenuation: + // - 0dB attenuation -> Maximum gain; + // - 0dB gain -> Maximum attenuation; + double att_dB = max_atten_dB - gain_db; + + // Check gain range + if (att_dB < min_atten_dB || att_dB > max_atten_dB) { + ERROR("Error port %d:%d the selected gain (%.2f dB) is out of range (%.2f to %.2f dB).\n", + q->card, + port_idx, + gain_db, + min_atten_dB, + max_atten_dB); + } + + // Calculate attenuation index + uint16_t att_index = (uint16_t)floor(att_dB * 4); + + // Bound index + att_index = SRSRAN_MIN(SRSRAN_MAX(att_index, q->param.tx_param->atten_quarter_db_min), + q->param.tx_param->atten_quarter_db_max); + + // Calculate equivalent gain + double actual_gain_dB = max_atten_dB - att_index * 0.25; + + // Set gain per port + if (port_idx >= q->nof_ports) { + for (uint8_t i = 0; i < q->nof_ports; i++) { + if (skiq_write_tx_attenuation(q->card, (skiq_tx_hdl_t)i, att_index)) { + ERROR("Error setting card %d:%d Tx attenuation\n", q->card, i); + } + } + } else { + if (skiq_write_tx_attenuation(q->card, (skiq_tx_hdl_t)port_idx, att_index)) { + ERROR("Error setting card %d:%d Tx attenuation\n", q->card, port_idx); + } + } + + return actual_gain_dB; +} + +double rf_skiq_card_set_rx_gain_db(rf_skiq_card_t* q, uint32_t port_idx, double gain_db) +{ + // Find the nearest gain index in the table + int gain_idx = -1; + double gain_min_diff = INFINITY; + for (int i = q->param.rx_param->gain_index_min; i <= q->param.rx_param->gain_index_max; i++) { + double gain_diff = fabs(q->rx_gain_table_db[i] - gain_db); + if (gain_diff < gain_min_diff) { + gain_min_diff = gain_diff; + gain_idx = i; + } + } + + if (gain_idx >= 0) { + gain_db = q->rx_gain_table_db[gain_idx]; + if (port_idx < q->nof_ports) { + // Set single port gain + q->issued_rx_gain_db[port_idx] = gain_db; + skiq_write_rx_gain(q->card, (skiq_rx_hdl_t)port_idx, gain_idx); + } else { + // Set all gains + for (int i = 0; i < q->nof_ports; i++) { + q->issued_rx_gain_db[i] = gain_db; + skiq_write_rx_gain(q->card, (skiq_rx_hdl_t)i, gain_idx); + } + } + } + + return gain_db; +} + +int rf_skiq_card_update_timestamp(rf_skiq_card_t* q, bool use_1pps, uint64_t new_ts) +{ + if (use_1pps) { + // Read 1pps source + skiq_1pps_source_t pps_source = skiq_1pps_source_unavailable; + if (skiq_read_1pps_source(q->card, &pps_source)) { + ERROR("Error reading card %d 1PPS source\n", q->card); + return SRSRAN_ERROR; + } + + // Make sure the source is external + if (pps_source != skiq_1pps_source_external) { + ERROR("Error card %d is not configured with external 1PPS source\n", q->card); + return SRSRAN_ERROR; + } + + // Get last time a PPS was received + uint64_t ts_sys_1pps = 0; + if (skiq_read_last_1pps_timestamp(q->card, NULL, &ts_sys_1pps)) { + ERROR("Reading card %d last 1PPS timestamp", q->card); + return SRSRAN_ERROR; + } + + // Read current system time + uint64_t ts = 0; + if (skiq_read_curr_sys_timestamp(q->card, &ts)) { + ERROR("Reading card %d system timestamp", q->card); + return SRSRAN_ERROR; + } + + // Make sure a 1PPS was received less than 2 seconds ago + if (ts - ts_sys_1pps > 2 * SKIQ_SYS_TIMESTAMP_FREQ) { + ERROR("Error card %d last PPS was received %.1f seconds ago (%ld - %ld)\n", + q->card, + (double)(ts - ts_sys_1pps) / (double)SKIQ_SYS_TIMESTAMP_FREQ, + ts, + ts_sys_1pps); + return SRSRAN_ERROR; + } + + // Set a given time in the future, a 100th of a second (10ms) + ts += SKIQ_SYS_TIMESTAMP_FREQ / 100; + + // Order that all timestamps are reseted when next 1PPS signal is received + SKIQ_RF_INFO(" ... Resetting card %d system timestamp on next PPS\n", q->card); + if (skiq_write_timestamp_update_on_1pps(q->card, ts, new_ts)) { + ERROR("Error reseting card %d timestamp on 1 PPS", q->card); + return SRSRAN_ERROR; + } + } else { + // Simply, reset timestamp + SKIQ_RF_INFO(" ... Resetting card %d system timestamp now\n", q->card); + if (skiq_update_timestamps(q->card, new_ts)) { + ERROR("Error resetting card %d timestamp", q->card); + return SRSRAN_ERROR; + } + } + return SRSRAN_SUCCESS; +} + +uint64_t rf_skiq_card_read_sys_timestamp(rf_skiq_card_t* q) +{ + uint64_t ts = 0UL; + + if (skiq_read_curr_sys_timestamp(q->card, &ts)) { + ERROR("Reading card %d system timestamp", q->card); + } + + return ts; +} + +uint64_t rf_skiq_card_read_rf_timestamp(rf_skiq_card_t* q) +{ + uint64_t ts = 0UL; + + if (skiq_read_curr_rx_timestamp(q->card, skiq_rx_hdl_A1, &ts)) { + ERROR("Reading card %d system timestamp", q->card); + } + + return ts; +} + +int rf_skiq_card_start_rx_streaming(rf_skiq_card_t* q, uint64_t timestamp) +{ + // Wrong state + if (q->state == RF_SKIQ_PORT_STATE_STOP) { + ERROR("Error starting Rx stream: wrong state (%d)\n", q->state); + return SRSRAN_ERROR; + } + + // Already enabled + if (q->state == RF_SKIQ_PORT_STATE_STREAMING) { + SKIQ_RF_INFO("Rx streams in card %d have already started\n", q->card); + return SRSRAN_SUCCESS; + } + + // Make a list with Rx handlers + skiq_rx_hdl_t rx_hdl[RF_SKIQ_MAX_PORTS_CARD]; + for (uint8_t i = 0; i < RF_SKIQ_MAX_PORTS_CARD; i++) { + rx_hdl[i] = (skiq_rx_hdl_t)i; + } + + pthread_mutex_lock(&q->mutex); + + // Start all Rx in a row + if (skiq_start_rx_streaming_multi_on_trigger(q->card, rx_hdl, q->nof_ports, skiq_trigger_src_synced, timestamp)) { + ERROR("Failed to start card %d Rx streaming\n", q->card); + return SRSRAN_ERROR; + } + + // Update state and broadcast condition variable + q->state = RF_SKIQ_PORT_STATE_STREAMING; + pthread_cond_broadcast(&q->cvar); + pthread_mutex_unlock(&q->mutex); + + SKIQ_RF_INFO("Rx streams in card %d have started\n", q->card); + + return SRSRAN_SUCCESS; +} + +int rf_skiq_card_stop_rx_streaming(rf_skiq_card_t* q) +{ + if (q->state == RF_SKIQ_PORT_STATE_STOP) { + ERROR("Error stopping Rx stream: wrong state (%d)\n", q->state); + return SRSRAN_ERROR; + } + + // Avoid stop streaming if it was not started + if (q->state == RF_SKIQ_PORT_STATE_IDLE) { + return SRSRAN_ERROR; + } + + // Make a list with Tx/Rx handlers + skiq_rx_hdl_t rx_hdl[RF_SKIQ_MAX_PORTS_CARD]; + for (uint8_t i = 0; i < RF_SKIQ_MAX_PORTS_CARD; i++) { + rx_hdl[i] = (skiq_rx_hdl_t)i; + } + + pthread_mutex_lock(&q->mutex); + + // Update state and broadcast condition variable first + q->state = RF_SKIQ_PORT_STATE_IDLE; + pthread_cond_broadcast(&q->cvar); + + // Stop all Rx in a row + if (skiq_stop_rx_streaming_multi_immediate(q->card, rx_hdl, q->nof_ports)) { + ERROR("Failed to stop card %d Rx streaming\n", q->card); + return SRSRAN_ERROR; + } + + pthread_mutex_unlock(&q->mutex); + + SKIQ_RF_INFO("Rx streams in card %d have stopped\n", q->card); + + return SRSRAN_SUCCESS; +} + +void rf_skiq_card_end_of_burst(rf_skiq_card_t* q) +{ + for (uint32_t i = 0; i < q->nof_ports; i++) { + rf_skiq_tx_port_end_of_burst(&q->tx_ports[i]); + } +} + +int rf_skiq_card_set_srate_hz(rf_skiq_card_t* q, uint32_t srate_hz) +{ + for (uint8_t i = 0; i < q->nof_ports; i++) { + // Set transmitter sampling rate + if (skiq_write_tx_sample_rate_and_bandwidth(q->card, (skiq_tx_hdl_t)i, srate_hz, srate_hz)) { + ERROR("Setting Tx sampling rate\n"); + } + + // Set receiver sampling rate + if (skiq_write_rx_sample_rate_and_bandwidth(q->card, (skiq_rx_hdl_t)i, srate_hz, srate_hz)) { + ERROR("Setting Rx sampling rate\n"); + } + + rf_skiq_rx_port_reset(&q->rx_ports[i]); + } + + return SRSRAN_SUCCESS; +} + +double rf_skiq_card_set_tx_freq_hz(rf_skiq_card_t* q, uint32_t port_idx, double freq_hz) +{ + q->suspend = true; + rf_skiq_tx_port_set_lo(&q->tx_ports[port_idx], (uint64_t)freq_hz); + q->suspend = false; + + return freq_hz; +} + +double rf_skiq_card_set_rx_freq_hz(rf_skiq_card_t* q, uint32_t port_idx, double freq_hz) +{ + q->suspend = true; + rf_skiq_rx_port_set_lo(&q->rx_ports[port_idx], (uint64_t)freq_hz); + q->suspend = false; + + // Update gains for only port 0 + if (port_idx == 0) { + // Update gain table + rf_skiq_card_update_gain_table(q); + + // Set previous issued gain in dB for the new tables + rf_skiq_card_set_rx_gain_db(q, q->nof_ports, q->issued_rx_gain_db[port_idx]); + } + + return freq_hz; +} + +void rf_skiq_card_close(rf_skiq_card_t* q) +{ + SKIQ_RF_INFO("Closing card %d...\n", q->card); + + // Post stop state to reader thread + q->state = RF_SKIQ_PORT_STATE_STOP; + pthread_cond_broadcast(&q->cvar); + + // Wait for reader thread to finish + pthread_join(q->thread, NULL); + + for (uint8_t i = 0; i < q->nof_ports; i++) { + rf_skiq_rx_port_free(&q->rx_ports[i]); + rf_skiq_tx_port_free(&q->tx_ports[i]); + } + + pthread_cond_destroy(&q->cvar); + pthread_mutex_destroy(&q->mutex); + + // Unlocks all cards + if (skiq_disable_cards(&q->card, 1)) { + ERROR("Unable to disable card %d\n", q->card); + } +} + +int rf_skiq_card_receive(rf_skiq_card_t* q, uint32_t port_idx, cf_t* dst, uint32_t nsamples, uint64_t* ts) +{ + // If suspended and samples are not available, then set all to zero + if (q->suspend && rf_skiq_rx_port_available(&q->rx_ports[port_idx]) == 0) { + srsran_vec_cf_zero(dst, nsamples); + *ts = 0UL; + return nsamples; + } + + return rf_skiq_rx_port_read(&q->rx_ports[port_idx], dst, nsamples, ts); +} + +uint64_t rf_skiq_card_get_rx_timestamp(rf_skiq_card_t* q, uint32_t port_idx) +{ + if (q->suspend || q->rx_ports[port_idx].rb_overflow) { + return 0UL; + } + + return rf_skiq_rx_port_get_timestamp(&q->rx_ports[port_idx]); +} + +bool rf_skiq_card_is_streaming(rf_skiq_card_t* q) +{ + return q->state == RF_SKIQ_PORT_STATE_STREAMING && !q->suspend; +} + +int rf_skiq_card_send(rf_skiq_card_t* q, uint32_t port_idx, const cf_t* data, uint32_t nsamples, uint64_t timestamp) +{ + // If suspended, do not bother the transmitter + if (q->suspend) { + return nsamples; + } + + return rf_skiq_tx_port_send(&q->tx_ports[port_idx], data, nsamples, timestamp); +} diff --git a/lib/src/phy/rf/rf_skiq_imp_card.h b/lib/src/phy/rf/rf_skiq_imp_card.h new file mode 100644 index 000000000..b63b82290 --- /dev/null +++ b/lib/src/phy/rf/rf_skiq_imp_card.h @@ -0,0 +1,77 @@ +/** + * + * \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. + * + */ +#ifndef SRSRAN_LIB_SRC_PHY_RF_RF_SKIQ_IMP_CARD_H_ +#define SRSRAN_LIB_SRC_PHY_RF_RF_SKIQ_IMP_CARD_H_ + +#include "rf_skiq_imp_port.h" + +typedef struct { + uint8_t card; + uint8_t nof_ports; + skiq_chan_mode_t mode; + + skiq_param_t param; + rf_skiq_tx_port_t tx_ports[RF_SKIQ_MAX_PORTS_CARD]; + rf_skiq_rx_port_t rx_ports[RF_SKIQ_MAX_PORTS_CARD]; + + double rx_gain_table_db[UINT8_MAX + 1]; + double cur_rx_gain_db; + double issued_rx_gain_db[SRSRAN_MAX_PORTS]; + bool suspend; + + uint64_t start_rx_stream_ts; + rf_skiq_port_state_t state; + pthread_mutex_t mutex; ///< Protect concurrent access to start/stop rx stream and receive + pthread_cond_t cvar; + pthread_t thread; + +} rf_skiq_card_t; + +int rf_skiq_card_init(rf_skiq_card_t* q, uint8_t card, uint8_t nof_ports, const rf_skiq_port_opts_t* opts); + +void rf_skiq_card_set_error_handler(rf_skiq_card_t* q, srsran_rf_error_handler_t error_handler, void* arg); + +int rf_skiq_card_update_gain_table(rf_skiq_card_t* q); + +double rf_skiq_card_set_tx_gain_db(rf_skiq_card_t* q, uint32_t port_idx, double gain_db); + +double rf_skiq_card_set_rx_gain_db(rf_skiq_card_t* q, uint32_t port_idx, double gain_db); + +int rf_skiq_card_update_timestamp(rf_skiq_card_t* q, bool use_1pps, uint64_t new_ts); + +uint64_t rf_skiq_card_read_sys_timestamp(rf_skiq_card_t* q); + +uint64_t rf_skiq_card_read_rf_timestamp(rf_skiq_card_t* q); + +int rf_skiq_card_start_rx_streaming(rf_skiq_card_t* q, uint64_t timestamp); + +int rf_skiq_card_stop_rx_streaming(rf_skiq_card_t* q); + +void rf_skiq_card_end_of_burst(rf_skiq_card_t* q); + +int rf_skiq_card_set_srate_hz(rf_skiq_card_t* q, uint32_t srate_hz); + +double rf_skiq_card_set_tx_freq_hz(rf_skiq_card_t* q, uint32_t port_idx, double freq_hz); + +double rf_skiq_card_set_rx_freq_hz(rf_skiq_card_t* q, uint32_t port_idx, double freq_hz); + +void rf_skiq_card_close(rf_skiq_card_t* q); + +int rf_skiq_card_receive(rf_skiq_card_t* q, uint32_t port_idx, cf_t* dst, uint32_t nsamples, uint64_t* ts); + +uint64_t rf_skiq_card_get_rx_timestamp(rf_skiq_card_t* q, uint32_t port_idx); + +bool rf_skiq_card_is_streaming(rf_skiq_card_t* q); + +int rf_skiq_card_send(rf_skiq_card_t* q, uint32_t port_idx, const cf_t* data, uint32_t nsamples, uint64_t timestamp); + +#endif // SRSRAN_LIB_SRC_PHY_RF_RF_SKIQ_IMP_CARD_H_ diff --git a/lib/src/phy/rf/rf_skiq_imp_cfg.h b/lib/src/phy/rf/rf_skiq_imp_cfg.h new file mode 100644 index 000000000..758a02ea0 --- /dev/null +++ b/lib/src/phy/rf/rf_skiq_imp_cfg.h @@ -0,0 +1,103 @@ +/** + * + * \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. + * + */ + +#ifndef SRSRAN_RF_SKIQ_IMP_CFG_H +#define SRSRAN_RF_SKIQ_IMP_CFG_H + +/** + * RF_SKIQ_MAX_PORTS_CARD sets the maximum number of ports per card + */ +#define RF_SKIQ_MAX_PORTS_CARD 2 + +/** + * SKIQ_CARD_SYNCH_MAX_TRIALS defines the maximum number of trials to synchronize multiple boards + */ +#define SKIQ_CARD_SYNCH_MAX_TRIALS 10 + +/** + * SKIQ_CARD_SYNC_MAX_ERROR sets the maximum number of system "ticks" error between boards during synchronization check. + * Consider the communication medium delay between the host and SidekIQ cards. + */ +#define SKIQ_CARD_SYNC_MAX_ERROR (SKIQ_SYS_TIMESTAMP_FREQ / 2) + +/** + * Maximum gap allowed in number of samples between ports + */ +#define SKIQ_PORT_SYNC_MAX_GAP (1024 * 1024) + +/** + * For checking the number of Tx lattes in the FPGA set the next line to the desired check period in number of blocks. + * Example: set to 1000 for checking it every 1000 blocks. + * WARNING: A low period may cause a reduction of performance in the Host-FPGA communication + */ +#define SKIQ_TX_LATES_CHECK_PERIOD (1920 * 10) + +/** + * Minimum number of channels that this RF device can reach + */ +#define SKIQ_MIN_CHANNELS (1) + +/** + * Maximum number of channels that this RF device can reach + */ +#define SKIQ_MAX_CHANNELS (SKIQ_MAX_NUM_CARDS * RF_SKIQ_MAX_PORTS_CARD) + +/** + * Dummy receive buffer size in samples + */ +#define RF_SKIQ_DUMMY_BUFFER_SIZE (1024) + +/** + * Magic word value as a ring buffer check + */ +#define SKIQ_RX_BUFFFER_MAGIC_WORD 0xABCD1234 + +/** + * Normalization value between fixed and floating point conversion + */ +#define SKIQ_NORM 2048.0 + +/** + * Default Rx gain in decibels (dB) + */ +#define SKIQ_RX_GAIN_DEFAULT_dB (+50.0f) + +/** + * Default sampling rate in samples per second (Hz) + */ +#define SKIQ_DEFAULT_SAMPLING_RATE_HZ (30.72e6) + +/** + * + */ +#define SKIQ_TX_PACKET_SIZE(N, MODE) (SKIQ_TX_PACKET_SIZE_INCREMENT_IN_WORDS * (N)-SKIQ_TX_HEADER_SIZE_IN_WORDS) + +/** + * SKIQ driver standard output MACRO + */ +extern uint32_t rf_skiq_logging_level; + +#define SKIQ_RF_INFO(...) \ + do { \ + if (rf_skiq_logging_level >= SKIQ_LOG_INFO) { \ + fprintf(stdout, "[SKIQ RF INFO] " __VA_ARGS__); \ + } \ + } while (false) + +#define SKIQ_RF_DEBUG(...) \ + do { \ + if (rf_skiq_logging_level >= SKIQ_LOG_DEBUG) { \ + fprintf(stdout, "[SKIQ RF INFO] " __VA_ARGS__); \ + } \ + } while (false) + +#endif // SRSRAN_RF_SKIQ_IMP_CFG_H diff --git a/lib/src/phy/rf/rf_skiq_imp_port.c b/lib/src/phy/rf/rf_skiq_imp_port.c new file mode 100644 index 000000000..c7244b8da --- /dev/null +++ b/lib/src/phy/rf/rf_skiq_imp_port.c @@ -0,0 +1,551 @@ +/** + * + * \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_skiq_imp_port.h" + +static void rf_skiq_rx_port_handle_overflow(rf_skiq_rx_port_t* q) +{ + srsran_rf_error_t error = {}; + + error.type = SRSRAN_RF_ERROR_OVERFLOW; + if (q->error_handler) { + q->error_handler(q->error_handler_arg, error); + } else { + SKIQ_RF_INFO("Rx overflow detected in %d:%d\n", q->card, (int)q->hdl); + } +} + +#if SKIQ_TX_LATES_CHECK_PERIOD +static bool rf_skiq_tx_port_handle_late(rf_skiq_tx_port_t* q) +{ + // Get number of lattes from FPGA + uint32_t total_late = 0; + if (skiq_read_tx_num_late_timestamps(q->card, q->hdl, &total_late)) { + ERROR("Error reading lates from port %d:%d\n", q->card, (int)q->hdl); + } + + // Calculate number of late timestamps + uint32_t new_late = total_late; + + // Remove previous read value + if (new_late >= q->last_total_late) { + new_late = new_late - q->last_total_late; + } + + // Update latest value + q->last_total_late = total_late; + + // No late, do not report them + if (new_late == 0) { + return false; + } + + if (q->error_handler) { + srsran_rf_error_t error = {}; + error.type = SRSRAN_RF_ERROR_LATE; + error.opt = new_late; + q->error_handler(q->error_handler_arg, error); + } else { + SKIQ_RF_INFO("Port %d late events detected in %d:%d\n", new_late, q->card, (int)q->hdl); + } + + return true; +} +#endif // SKIQ_TX_LATES_CHECK_PERIOD + +static void* writer_thread(void* arg) +{ + uint64_t last_tx_ts = 0; + rf_skiq_tx_port_t* q = (rf_skiq_tx_port_t*)arg; + skiq_tx_block_t* p_tx_block = NULL; + + if (skiq_start_tx_streaming(q->card, q->hdl)) { + ERROR("Error starting Tx stream %d:%d\n", q->card, (int)q->hdl); + return NULL; + } + + q->state = RF_SKIQ_PORT_STATE_STREAMING; + + while (q->state != RF_SKIQ_PORT_STATE_STOP) { + // Read block from ring-buffer + int n = srsran_ringbuffer_read_block(&q->rb, (void**)&p_tx_block, q->p_block_nbytes, 1000); + + // Stop state is detected + if (q->state == RF_SKIQ_PORT_STATE_STOP) { + break; + } + + // Ignore blocks with TS=0 + if (q->p_tx_block->timestamp == 0) { + continue; + } + + // Check if the timestamp is the past (this can be caused by sample rate change) + if (last_tx_ts > q->p_tx_block->timestamp) { + skiq_read_curr_tx_timestamp(q->card, q->hdl, &last_tx_ts); + if (last_tx_ts > q->p_tx_block->timestamp) { + ERROR("Tx block (ts=%ld) is in the past (%ld), ignoring\n", q->p_tx_block->timestamp, last_tx_ts); + continue; + } + } + last_tx_ts = q->p_tx_block->timestamp + q->block_size; + + // If the ring-buffer did not return with error code... + if (n > SRSRAN_SUCCESS) { + SKIQ_RF_DEBUG( + "[Tx %d:%d block] ts=%ld; nsamples=%d;\n", q->card, (int)q->hdl, p_tx_block->timestamp, q->block_size); + + if (skiq_transmit(q->card, q->hdl, p_tx_block, NULL) < 0) { + ERROR("Error transmitting card %d\n", q->card); + q->state = RF_SKIQ_PORT_STATE_STOP; + } + +#if SKIQ_TX_LATES_CHECK_PERIOD + if (q->last_check_ts + SKIQ_TX_LATES_CHECK_PERIOD < p_tx_block->timestamp) { + // Handle late timestamps events + rf_skiq_tx_port_handle_late(q); + + // Update last check TS + q->last_check_ts = p_tx_block->timestamp; + } +#endif // SKIQ_TX_LATES_CHECK_PERIOD + } + } + + if (skiq_stop_tx_streaming(q->card, q->hdl)) { + ERROR("Error stopping Tx stream %d:%d\n", q->card, (int)q->hdl); + } + + SKIQ_RF_INFO("Exiting writer thread!\n"); + + return NULL; +} + +int rf_skiq_tx_port_init(rf_skiq_tx_port_t* q, uint8_t card, skiq_tx_hdl_t hdl, const rf_skiq_port_opts_t* opts) +{ + // Defines the block size in multiples of 256 words + uint32_t nof_blocks_per_packet = 4; + switch (opts->stream_mode) { + case skiq_rx_stream_mode_high_tput: + nof_blocks_per_packet = 8; + break; + case skiq_rx_stream_mode_low_latency: + nof_blocks_per_packet = 2; + break; + case skiq_rx_stream_mode_balanced: + default: + // Keep default value + break; + } + + q->card = card; + q->hdl = hdl; + q->block_size = SKIQ_TX_PACKET_SIZE(nof_blocks_per_packet, opts->chan_mode); + q->p_block_nbytes = q->block_size * 4 + SKIQ_TX_HEADER_SIZE_IN_BYTES; + + // configure the data flow mode to use timestamps + if (skiq_write_tx_data_flow_mode(card, hdl, skiq_tx_with_timestamps_data_flow_mode) != 0) { + ERROR("Setting Tx data flow mode"); + return SRSRAN_ERROR; + } + + // configure the transfer mode to synchronous + if (skiq_write_tx_transfer_mode(card, hdl, skiq_tx_transfer_mode_sync) != 0) { + ERROR("setting tx transfer mode"); + return SRSRAN_ERROR; + } + + // configure Tx block size + if (skiq_write_tx_block_size(card, hdl, q->block_size) != 0) { + ERROR("configuring Tx block size"); + return SRSRAN_ERROR; + } + + q->p_tx_block = skiq_tx_block_allocate(q->block_size); + if (q->p_tx_block == NULL) { + ERROR("Allocating Tx block"); + return SRSRAN_ERROR; + } + + // initialise ring buffer + if (srsran_ringbuffer_init(&q->rb, (int)(opts->tx_rb_size * q->p_block_nbytes))) { + ERROR("Initialising ringbuffer"); + return SRSRAN_ERROR; + } + + // Initialise thread parameters + pthread_attr_t attr; + struct sched_param param; + + param.sched_priority = sched_get_priority_max(SCHED_FIFO); + pthread_attr_init(&attr); + if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) { + ERROR("Error not enough privileges to set Scheduling priority\n"); + } + + if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) { + ERROR("Error not enough privileges to set Scheduling priority\n"); + } + + if (pthread_attr_setschedparam(&attr, ¶m)) { + ERROR("Error not enough privileges to set Scheduling priority\n"); + } + + // Launch thread + if (pthread_create(&q->thread, &attr, writer_thread, q)) { + ERROR("Error creating writer thread with attributes (Did you miss sudo?). Trying without attributes.\n"); + return SRSRAN_ERROR; + } + + // Rename thread + char thread_name[32] = {}; + if (snprintf(thread_name, sizeof(thread_name), "SKIQ Tx %d:%d", q->card, (int)q->hdl) > 0) { + pthread_setname_np(q->thread, thread_name); + } + + return SRSRAN_SUCCESS; +} + +void rf_skiq_tx_port_free(rf_skiq_tx_port_t* q) +{ + // Stop thread + q->state = RF_SKIQ_PORT_STATE_STOP; + + // Unlock ringbuffer + srsran_ringbuffer_write(&q->rb, (void*)q->p_tx_block, q->p_block_nbytes); + + // Wait thread to return + pthread_join(q->thread, NULL); + + if (q->p_tx_block) { + skiq_tx_block_free(q->p_tx_block); + } + srsran_ringbuffer_free(&q->rb); +} + +void rf_skiq_tx_port_end_of_burst(rf_skiq_tx_port_t* q) +{ + pthread_mutex_lock(&q->mutex); + + // Fill pending block if any, otherwise push a block with zeros + if (q->next_offset > 0) { + // Calculate pending samples to fill the block + uint32_t pending = q->block_size - q->next_offset; + + // Zero pending samples in the block + srsran_vec_i16_zero(&q->p_tx_block->data[q->next_offset * 2], 2 * pending); + + // Write block into the ring-buffer + srsran_ringbuffer_write_block(&q->rb, q->p_tx_block, q->p_block_nbytes); + + SKIQ_RF_DEBUG("[Tx %d:%d] Padding offset=%d; n=%d; ts=%ld\n", + q->card, + (int)q->hdl, + q->next_offset, + pending, + q->p_tx_block->timestamp); + + // Reset next offset, so next transmission uses a new block + q->next_offset = 0; + } + + pthread_mutex_unlock(&q->mutex); +} + +int rf_skiq_tx_port_send(rf_skiq_tx_port_t* q, const cf_t* buffer, uint32_t nsamples, uint64_t ts) +{ + // Ignore transmission if the stream is not enabled + if (q->state != RF_SKIQ_PORT_STATE_STREAMING) { + return nsamples; + } + + pthread_mutex_lock(&q->mutex); + + // Calculate destination where IQ shall be stored + int16_t* p_tx_iq = &q->p_tx_block->data[q->next_offset * 2]; + + // Calculate number of samples to take from buffer + nsamples = SRSRAN_MIN(nsamples, q->block_size - q->next_offset); + + // Set time stamp only if no offset + if (q->next_offset == 0) { + skiq_tx_set_block_timestamp(q->p_tx_block, ts); + } + + SKIQ_RF_DEBUG( + "[Tx %d:%d] Write offset=%d; nsamples=%d; ts=%ld\n", q->card, (int)q->hdl, q->next_offset, nsamples, ts); + + // Fill data ... + if (buffer == NULL) { + // ... with zeros + srsran_vec_i16_zero(p_tx_iq, 2 * nsamples); + } else { + // ... with samples, after conversion + srsran_vec_convert_conj_cs(buffer, SKIQ_NORM, p_tx_iq, nsamples); + } + q->next_offset += nsamples; + + // If the number of samples does not fill the block, return early + if (q->next_offset < q->block_size) { + pthread_mutex_unlock(&q->mutex); + return nsamples; + } + + if (srsran_ringbuffer_space(&q->rb) < q->p_block_nbytes * 2) { + ERROR("Tx buffer overflow\n"); + pthread_mutex_unlock(&q->mutex); + return nsamples; + } + + // Actual write in ring buffer + int n = srsran_ringbuffer_write_timed_block(&q->rb, q->p_tx_block, q->p_block_nbytes, 5); + + pthread_mutex_unlock(&q->mutex); + + // In case of error (e.g. timeout) return code + if (n < SRSRAN_SUCCESS) { + return n; + } + + // In case of number of bytes mismatch return error + if (n != q->p_block_nbytes) { + ERROR("Error writing in Tx buffer %d:%d\n", q->card, (int)q->hdl); + return SRSRAN_ERROR; + } + + // Reset offset only if the block was successfully written into the ring-buffer + q->next_offset = 0; + + // Return the number of samples writen in the buffer + return nsamples; +} + +void rf_skiq_tx_port_set_error_handler(rf_skiq_tx_port_t* q, srsran_rf_error_handler_t error_handler, void* arg) +{ + q->error_handler = error_handler; + q->error_handler_arg = arg; +} + +int rf_skiq_tx_port_set_lo(rf_skiq_tx_port_t* q, uint64_t lo_freq) +{ + // Skip setting LO frequency if it is not required + if (q->current_lo == lo_freq) { + return SRSRAN_SUCCESS; + } + + skiq_filt_t filt = (lo_freq < 3000000000UL) ? skiq_filt_0_to_3000_MHz : skiq_filt_3000_to_6000_MHz; + + if (skiq_write_tx_LO_freq(q->card, q->hdl, lo_freq)) { + ERROR("Setting card %d:%d Tx Lo frequency", q->card, (int)q->hdl); + return SRSRAN_ERROR; + } + + if (skiq_write_tx_filter_path(q->card, q->hdl, filt)) { + ERROR("Setting card %d:%d Tx filter", q->card, q->hdl); + return SRSRAN_ERROR; + } + + q->current_lo = lo_freq; + + return SRSRAN_SUCCESS; +} + +int rf_skiq_rx_port_init(rf_skiq_rx_port_t* q, uint8_t card, skiq_rx_hdl_t hdl, const rf_skiq_port_opts_t* opts) +{ + q->card = card; + q->hdl = hdl; + + // enabling DC offset correction can cause an IQ impairment + if (skiq_write_rx_dc_offset_corr(card, hdl, false)) { + ERROR("Setting RX DC offset correction"); + return SRSRAN_ERROR; + } + + // set rx gain mode + if (skiq_write_rx_gain_mode(card, hdl, skiq_rx_gain_manual)) { + ERROR("Setting RX gain mode"); + return SRSRAN_ERROR; + } + + // Rx block size in bytes + int32_t rx_block_size = skiq_read_rx_block_size(q->card, opts->stream_mode) - SKIQ_RX_HEADER_SIZE_IN_BYTES; + + // initialise ring buffer + if (srsran_ringbuffer_init(&q->rb, (int)(opts->rx_rb_size * rx_block_size))) { + ERROR("Initialising ringbuffer"); + return SRSRAN_ERROR; + } + + return SRSRAN_SUCCESS; +} + +void rf_skiq_rx_port_free(rf_skiq_rx_port_t* q) +{ + srsran_ringbuffer_free(&q->rb); +} + +int rf_skiq_rx_port_available(rf_skiq_rx_port_t* q) +{ + return srsran_ringbuffer_status(&q->rb); +} + +int rf_skiq_rx_port_read(rf_skiq_rx_port_t* q, cf_t* dest, uint32_t nsamples, uint64_t* ts_start) +{ + // Detect start of new block + if (q->rb_read_rem == 0) { + skiq_header_t header = {}; + + // If ring-buffer overflow was detected... + if (q->rb_overflow) { + // Reset ring buffer + srsran_ringbuffer_reset(&q->rb); + + // Clear overflow flag + q->rb_overflow = false; + + // Set samples to zero + srsran_vec_cf_zero(dest, nsamples); + + // Set default timestamp + *ts_start = 0; + + // Since the buffer is empty, return the full amount of samples so it does not delay reception of other channels + return nsamples; + } + + // Read a packet. First the header + if (srsran_ringbuffer_read(&q->rb, &header, sizeof(skiq_header_t)) != sizeof(skiq_header_t)) { + ERROR("Error reading header from ring-buffer %d:%d corrupted\n", q->card, (int)q->hdl); + return SRSRAN_ERROR; + } + + // Check header magic word + if (header.magic != SKIQ_RX_BUFFFER_MAGIC_WORD) { + ERROR("Error ring-buffer %d:%d corrupted\n", q->card, (int)q->hdl); + return SRSRAN_ERROR; + } + + // Successful read + q->rb_read_rem = header.nsamples; + q->rb_tstamp_rem = header.tstamp; + } + + // Limit number of samples to the remainder of the stored packet + nsamples = SRSRAN_MIN(q->rb_read_rem, nsamples); + + // Read any remainder of a packet from the ring buffer + int n = srsran_ringbuffer_read_convert_conj(&q->rb, dest, SKIQ_NORM, nsamples); + + // Detect error in read + if (n < SRSRAN_SUCCESS) { + ERROR("Error reading packet remainder from %d:%d\n", q->card, (int)q->hdl); + return SRSRAN_ERROR; + } + + SKIQ_RF_DEBUG("[Rx %d:%d] Read nsamples=%d/%d; ts=%ld\n", q->card, (int)q->hdl, n, nsamples, q->rb_tstamp_rem); + + // Update timestamp + *ts_start = q->rb_tstamp_rem; + + // Update reminder + q->rb_read_rem -= n; + q->rb_tstamp_rem += n; + + // Return number of read samples + return n; +} + +uint64_t rf_skiq_rx_port_get_timestamp(rf_skiq_rx_port_t* q) +{ + return q->rb_tstamp_rem; +} + +int rf_skiq_rx_port_write(rf_skiq_rx_port_t* q, const skiq_rx_block_t* p_rx_block, uint32_t nsamples) +{ + // Prepare header + skiq_header_t header = {}; + header.magic = SKIQ_RX_BUFFFER_MAGIC_WORD; + header.tstamp = p_rx_block->rf_timestamp; + header.nsamples = nsamples; + + // Ignore block if the overflow flag has risen + if (q->rb_overflow) { + return nsamples; + } + + SKIQ_RF_DEBUG("[Rx %d:%d block] ts=%ld; nsamples=%d;\n", q->card, (int)q->hdl, header.tstamp, header.nsamples); + + // Check space in the ring-buffer prior to writing + if (srsran_ringbuffer_space(&q->rb) >= sizeof(skiq_header_t) + nsamples * 4) { + // Write header + if (srsran_ringbuffer_write_block(&q->rb, &header, sizeof(skiq_header_t)) != sizeof(skiq_header_t)) { + ERROR("Writing header in Rx buffer %d:%d!\n", q->card, (int)q->hdl); + return SRSRAN_ERROR; + } + + // Write IQ samples + if (srsran_ringbuffer_write_block(&q->rb, (uint8_t*)p_rx_block->data, (int)nsamples * 4) != nsamples * 4) { + ERROR("Writing base-band in Rx buffer %d:%d!\n", q->card, (int)q->hdl); + return SRSRAN_ERROR; + } + } else { + SKIQ_RF_INFO("Rx %d:%d ring-buffer overflow!\n", q->card, (int)q->hdl); + q->rb_overflow = true; + rf_skiq_rx_port_handle_overflow(q); + } + + // Process overload, call handle only for rising-edges + if (!q->rf_overflow && p_rx_block->overload) { + rf_skiq_rx_port_handle_overflow(q); + } + q->rf_overflow = p_rx_block->overload; + + return nsamples; +} + +void rf_skiq_rx_port_set_error_handler(rf_skiq_rx_port_t* q, srsran_rf_error_handler_t error_handler, void* arg) +{ + q->error_handler = error_handler; + q->error_handler_arg = arg; +} + +void rf_skiq_rx_port_reset(rf_skiq_rx_port_t* q) +{ + SKIQ_RF_INFO("Rx port %d:%d reset\n", q->card, (int)q->hdl); + q->rb_read_rem = 0; + q->rb_tstamp_rem = 0; + srsran_ringbuffer_reset(&q->rb); +} + +int rf_skiq_rx_port_set_lo(rf_skiq_rx_port_t* q, uint64_t lo_freq) +{ + // Skip setting LO frequency if it is not required + if (q->current_lo == lo_freq) { + return SRSRAN_SUCCESS; + } + + skiq_filt_t filt = (lo_freq < 3000000000UL) ? skiq_filt_0_to_3000_MHz : skiq_filt_3000_to_6000_MHz; + + if (skiq_write_rx_LO_freq(q->card, q->hdl, lo_freq)) { + ERROR("Setting card %d:%d Tx Lo frequency", q->card, (int)q->hdl); + return SRSRAN_ERROR; + } + + if (skiq_write_rx_preselect_filter_path(q->card, q->hdl, filt)) { + ERROR("Setting card %d:%d Tx filter", q->card, q->hdl); + return SRSRAN_ERROR; + } + + q->current_lo = lo_freq; + + return SRSRAN_SUCCESS; +} \ No newline at end of file diff --git a/lib/src/phy/rf/rf_skiq_imp_port.h b/lib/src/phy/rf/rf_skiq_imp_port.h new file mode 100644 index 000000000..87deb8340 --- /dev/null +++ b/lib/src/phy/rf/rf_skiq_imp_port.h @@ -0,0 +1,100 @@ +/** + * + * \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. + * + */ + +#ifndef SRSRAN_RF_SKIQ_IMP_PORT_H +#define SRSRAN_RF_SKIQ_IMP_PORT_H + +#include +#include + +#include "rf_helper.h" +#include "rf_skiq_imp_cfg.h" +#include "rf_skiq_imp_port.h" +#include "srsran/srsran.h" + +typedef struct { + uint32_t magic; + uint64_t tstamp; + uint32_t nsamples; +} skiq_header_t; + +typedef enum { + RF_SKIQ_PORT_STATE_IDLE = 0, + RF_SKIQ_PORT_STATE_STREAMING, + RF_SKIQ_PORT_STATE_STOP +} rf_skiq_port_state_t; + +typedef struct { + uint32_t tx_rb_size; + uint32_t rx_rb_size; + skiq_chan_mode_t chan_mode; + char stream_mode_str[RF_PARAM_LEN]; + skiq_rx_stream_mode_t stream_mode; +} rf_skiq_port_opts_t; + +typedef struct { + uint8_t card; + skiq_tx_hdl_t hdl; + skiq_tx_block_t* p_tx_block; + uint32_t p_block_nbytes; // Size in bytes including header + uint32_t block_size; // Size in words (samples) + uint32_t next_offset; // Number of samples remainder + srsran_ringbuffer_t rb; + rf_skiq_port_state_t state; + pthread_t thread; + pthread_mutex_t mutex; // Protects p_tx_block + + uint64_t current_lo; + + srsran_rf_error_handler_t error_handler; + void* error_handler_arg; + +#if SKIQ_TX_LATES_CHECK_PERIOD + uint64_t last_check_ts; + uint32_t last_total_late; + uint32_t last_total_underruns; +#endif // SKIQ_TX_LATES_CHECK_PERIOD +} rf_skiq_tx_port_t; + +int rf_skiq_tx_port_init(rf_skiq_tx_port_t* q, uint8_t card, skiq_tx_hdl_t hdl, const rf_skiq_port_opts_t* opts); +void rf_skiq_tx_port_free(rf_skiq_tx_port_t* q); +void rf_skiq_tx_port_end_of_burst(rf_skiq_tx_port_t* q); +int rf_skiq_tx_port_send(rf_skiq_tx_port_t* q, const cf_t* buffer, uint32_t nsamples, uint64_t ts); +void rf_skiq_tx_port_set_error_handler(rf_skiq_tx_port_t* q, srsran_rf_error_handler_t error_handler, void* arg); +int rf_skiq_tx_port_set_lo(rf_skiq_tx_port_t* q, uint64_t lo_freq); + +typedef struct { + uint8_t card; + skiq_rx_hdl_t hdl; + srsran_ringbuffer_t rb; + uint32_t rb_read_rem; + uint64_t rb_tstamp_rem; + bool rf_overflow; ///< Indicates an RF message was flagged with overflow + bool rb_overflow; ///< Indicates that ring-buffer is full and it needs to be flushed + + uint64_t current_lo; + + srsran_rf_error_handler_t error_handler; + void* error_handler_arg; +} rf_skiq_rx_port_t; + +int rf_skiq_rx_port_init(rf_skiq_rx_port_t* q, uint8_t card, skiq_rx_hdl_t hdl, const rf_skiq_port_opts_t* opts); +void rf_skiq_rx_port_free(rf_skiq_rx_port_t* q); +int rf_skiq_rx_port_available(rf_skiq_rx_port_t* q); +int rf_skiq_rx_port_read(rf_skiq_rx_port_t* q, cf_t* dest, uint32_t nsamples, uint64_t* ts_start); +uint64_t rf_skiq_rx_port_get_timestamp(rf_skiq_rx_port_t* q); +int rf_skiq_rx_port_write(rf_skiq_rx_port_t* q, const skiq_rx_block_t* p_rx_block, uint32_t nbytes); +void rf_skiq_rx_port_set_error_handler(rf_skiq_rx_port_t* q, srsran_rf_error_handler_t error_handler, void* arg); +void rf_skiq_rx_port_reset(rf_skiq_rx_port_t* q); +int rf_skiq_rx_port_set_lo(rf_skiq_rx_port_t* q, uint64_t lo_freq); + +#endif // SRSRAN_RF_SKIQ_IMP_PORT_H diff --git a/lib/src/phy/rf/skiq_pps_test.c b/lib/src/phy/rf/skiq_pps_test.c new file mode 100644 index 000000000..5ebd058b5 --- /dev/null +++ b/lib/src/phy/rf/skiq_pps_test.c @@ -0,0 +1,133 @@ +/** + * + * \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 +#include +#include + +#define ERROR(...) fprintf(stderr, __VA_ARGS__); + +const static uint8_t nof_cards = 1; + +int main(int argc, char* argv[]) +{ + int ret = -1; + uint8_t card = 0; + + if (argc > 1) { + card = (uint8_t)strtol(argv[1], NULL, 10); + } + + uint8_t nof_available_cards = 0; + uint8_t available_cards[SKIQ_MAX_NUM_CARDS] = {}; + if (skiq_get_cards(skiq_xport_type_auto, &nof_available_cards, available_cards)) { + ERROR("Getting available cards\n"); + goto clean_exit; + } + + // Check number of cards bounds + if (nof_cards > nof_available_cards) { + ERROR("The number of cards (%d) exceeds available cards (%d)\n", nof_cards, nof_available_cards); + goto clean_exit; + } + + // Initialise driver + if (skiq_init(skiq_xport_type_auto, skiq_xport_init_level_full, &card, 1)) { + ERROR("Unable to initialise libsidekiq driver\n"); + goto clean_exit; + } + + // Programming FPGA from flash ensures the FPGA and transceiver are completely reseted + if (skiq_prog_fpga_from_flash(card)) { + ERROR("Error programming FPGA from flash\n"); + goto clean_exit; + } + + // Read 1pps source + skiq_1pps_source_t pps_source = skiq_1pps_source_unavailable; + if (skiq_read_1pps_source(card, &pps_source)) { + ERROR("Error reading card %d 1PPS source\n", card); + goto clean_exit; + } + + // Make sure the source is external + if (pps_source != skiq_1pps_source_external) { + ERROR("Error card %d is not configured with external 1PPS source\n", card); + goto clean_exit; + } + + // Sleeping 5 seconds + sleep(5); + + // Get last time a PPS was received + uint64_t ts_sys_1pps = 0; + if (skiq_read_last_1pps_timestamp(card, NULL, &ts_sys_1pps)) { + ERROR("Reading card %d last 1PPS timestamp", card); + goto clean_exit; + } + + // Read current system time + uint64_t ts = 0; + if (skiq_read_curr_sys_timestamp(card, &ts)) { + ERROR("Reading card %d system timestamp", card); + goto clean_exit; + } + + // Make sure a 1PPS was received less than 2 seconds ago + if (ts - ts_sys_1pps > 2 * SKIQ_SYS_TIMESTAMP_FREQ) { + ERROR("Error card %d last PPS was received %.1f seconds ago (ts=%ld)\n", + card, + (double)(ts - ts_sys_1pps) / (double)SKIQ_SYS_TIMESTAMP_FREQ, + ts_sys_1pps); + goto clean_exit; + } + + // Set a given time in the future, a 100th of a second (10ms) + ts += SKIQ_SYS_TIMESTAMP_FREQ / 100; + + // Reset timestamp in a near future on next PPS signal + if (skiq_write_timestamp_reset_on_1pps(card, ts)) { + ERROR("Error reseting card %d timestamp on 1 PPS", card); + goto clean_exit; + } + + // Give time to pass 1PPS + sleep(1); + + // Read current system time + if (skiq_read_curr_sys_timestamp(card, &ts)) { + ERROR("Reading card %d system timestamp", card); + goto clean_exit; + } + + // The current system timestamp should be below 2s + if (ts > 2 * SKIQ_SYS_TIMESTAMP_FREQ) { + ERROR("Timestamp of card %d is greater than 2 seconds (%.1fs)!\n", + card, + (double)ts / (double)SKIQ_SYS_TIMESTAMP_FREQ); + goto clean_exit; + } + + // Success + printf("Success!\n"); + ret = 0; + +clean_exit: + if (skiq_disable_cards(&card, 1)) { + ERROR("Unable to disable cards\n"); + } + + // Close sidekiq SDK + skiq_exit(); + + return ret; +}