SIDEKIQ: Initial implementation

SIDEKIQ: Add SKIQ_FOUND to RF found condition

SIDEKIQ: finished SKIQ component abstraction

SIDEKIQ: fix issues and added external PPS example

SIDEKIQ: add PPS test card index argument

SIDEKIQ: improvements

SIDEKIQ: improved srate change

SIDEKIQ: more improvements

SIDEKIQ: more fixes

SIDEKIQ: fix Rx ch gain

SIDEKIQ: Fix multi-card synchronism

SIDEKIQ: Better Rx gain tracking
This commit is contained in:
Xavier Arteaga 2020-09-14 08:33:21 +02:00 committed by Xavier Arteaga
parent 1098d9d444
commit 96ee4b7258
12 changed files with 2703 additions and 3 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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 <unistd.h>
#include <sidekiq_api.h>
#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;
}

View File

@ -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 <stdbool.h>
#include <stdint.h>
#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);

View File

@ -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, &param)) {
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);
}

View File

@ -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_

View File

@ -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

View File

@ -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, &param)) {
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;
}

View File

@ -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 <sidekiq_api.h>
#include <srsran/phy/rf/rf.h>
#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

View File

@ -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 <sidekiq_api.h>
#include <stdio.h>
#include <unistd.h>
#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;
}