mirror of https://github.com/pvnis/srsRAN_4G.git
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 trackingmaster
parent
1098d9d444
commit
96ee4b7258
@ -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)
|
@ -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;
|
||||||
|
}
|
@ -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);
|
@ -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);
|
||||||
|
}
|
@ -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_
|
@ -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
|
@ -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;
|
||||||
|
}
|
@ -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
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue