mirror of https://github.com/pvnis/srsRAN_4G.git
Merge branch 'next' into agpl_next
commit
635d0bc6e9
@ -0,0 +1,66 @@
|
||||
#
|
||||
# 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(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,172 @@
|
||||
/**
|
||||
*
|
||||
* \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_SLOT_POINT_H
|
||||
#define SRSRAN_SLOT_POINT_H
|
||||
|
||||
#include "srsran/adt/interval.h"
|
||||
#include "srsran/common/srsran_assert.h"
|
||||
|
||||
namespace srsran {
|
||||
|
||||
class slot_point
|
||||
{
|
||||
uint32_t numerology_ : 3;
|
||||
uint32_t count_ : 29;
|
||||
|
||||
const static uint8_t NOF_NUMEROLOGIES = 5;
|
||||
const static uint16_t NOF_SFNS = 1024;
|
||||
const static uint8_t NOF_SUBFRAMES_PER_FRAME = 10;
|
||||
|
||||
uint32_t nof_slots_per_hf() const { return nof_slots_per_frame() * NOF_SFNS; }
|
||||
|
||||
public:
|
||||
slot_point() : numerology_(NOF_NUMEROLOGIES), count_(0) {}
|
||||
slot_point(uint8_t numerology, uint32_t count) : numerology_(numerology), count_(count)
|
||||
{
|
||||
srsran_assert(numerology < NOF_NUMEROLOGIES, "Invalid numerology idx=%d passed", (int)numerology);
|
||||
srsran_assert(count < nof_slots_per_hf(), "Invalid slot count=%d passed", (int)count);
|
||||
}
|
||||
slot_point(uint8_t numerology, uint16_t sfn_val, uint8_t slot) :
|
||||
numerology_(numerology), count_(slot + sfn_val * nof_slots_per_frame())
|
||||
{
|
||||
srsran_assert(numerology < NOF_NUMEROLOGIES, "Invalid numerology idx=%d passed", (int)numerology);
|
||||
srsran_assert(sfn_val < NOF_SFNS, "Invalid SFN=%d provided", (int)sfn_val);
|
||||
srsran_assert(slot < nof_slots_per_frame(),
|
||||
"Slot index=%d exceeds maximum number of slots=%d",
|
||||
(int)slot,
|
||||
(int)nof_slots_per_frame());
|
||||
}
|
||||
|
||||
bool valid() const { return numerology_ < NOF_NUMEROLOGIES; }
|
||||
uint8_t nof_slots_per_subframe() const { return 1U << numerology_; }
|
||||
uint8_t nof_slots_per_frame() const { return nof_slots_per_subframe() * NOF_SUBFRAMES_PER_FRAME; }
|
||||
|
||||
uint16_t sfn() const { return count_ / nof_slots_per_frame(); }
|
||||
uint16_t subframe_idx() const { return slot_idx() / nof_slots_per_subframe(); }
|
||||
uint8_t slot_idx() const { return count_ % nof_slots_per_frame(); }
|
||||
uint8_t numerology_idx() const { return numerology_; }
|
||||
uint32_t to_uint() const { return count_; }
|
||||
explicit operator uint32_t() const { return count_; }
|
||||
|
||||
void clear() { numerology_ = NOF_NUMEROLOGIES; }
|
||||
|
||||
// operators
|
||||
bool operator==(const slot_point& other) const { return other.count_ == count_ and other.numerology_ == numerology_; }
|
||||
bool operator!=(const slot_point& other) const { return not(*this == other); }
|
||||
bool operator<(const slot_point& other) const
|
||||
{
|
||||
srsran_assert(numerology_idx() == other.numerology_idx(), "Comparing slots of different numerologies");
|
||||
int a = static_cast<int>(other.count_) - static_cast<int>(count_);
|
||||
if (a > 0) {
|
||||
return (a < (int)nof_slots_per_hf() / 2);
|
||||
}
|
||||
return (a < -(int)nof_slots_per_hf() / 2);
|
||||
}
|
||||
bool operator<=(const slot_point& other) const { return (*this == other) or (*this < other); }
|
||||
bool operator>=(const slot_point& other) const { return not(*this < other); }
|
||||
bool operator>(const slot_point& other) const { return (*this != other) and *this >= other; }
|
||||
|
||||
int32_t operator-(const slot_point& other) const
|
||||
{
|
||||
int a = static_cast<int>(count_) - static_cast<int>(other.count_);
|
||||
if (a >= (int)nof_slots_per_hf() / 2) {
|
||||
return a - nof_slots_per_hf();
|
||||
}
|
||||
if (a < -(int)nof_slots_per_hf() / 2) {
|
||||
return a + nof_slots_per_hf();
|
||||
}
|
||||
return a;
|
||||
}
|
||||
slot_point& operator++()
|
||||
{
|
||||
count_++;
|
||||
if (count_ == nof_slots_per_hf()) {
|
||||
count_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
slot_point operator++(int)
|
||||
{
|
||||
slot_point ret{*this};
|
||||
this-> operator++();
|
||||
return ret;
|
||||
}
|
||||
slot_point& operator+=(uint32_t jump)
|
||||
{
|
||||
count_ = (count_ + jump) % nof_slots_per_hf();
|
||||
return *this;
|
||||
}
|
||||
slot_point& operator-=(uint32_t jump)
|
||||
{
|
||||
int a = (static_cast<int>(count_) - static_cast<int>(jump)) % static_cast<int>(nof_slots_per_hf());
|
||||
if (a < 0) {
|
||||
a += nof_slots_per_hf();
|
||||
}
|
||||
count_ = a;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool is_in_interval(slot_point begin, slot_point end) const { return (*this >= begin and *this < end); }
|
||||
};
|
||||
inline slot_point operator+(slot_point slot, uint32_t jump)
|
||||
{
|
||||
slot += jump;
|
||||
return slot;
|
||||
}
|
||||
inline slot_point operator+(uint32_t jump, slot_point slot)
|
||||
{
|
||||
slot += jump;
|
||||
return slot;
|
||||
}
|
||||
inline slot_point operator-(slot_point slot, uint32_t jump)
|
||||
{
|
||||
slot -= jump;
|
||||
return slot;
|
||||
}
|
||||
inline slot_point max(slot_point s1, slot_point s2)
|
||||
{
|
||||
return s1 > s2 ? s1 : s2;
|
||||
}
|
||||
inline slot_point min(slot_point s1, slot_point s2)
|
||||
{
|
||||
return s1 < s2 ? s1 : s2;
|
||||
}
|
||||
|
||||
using slot_interval = srsran::interval<slot_point>;
|
||||
|
||||
} // namespace srsran
|
||||
|
||||
namespace fmt {
|
||||
template <>
|
||||
struct formatter<srsran::slot_point> {
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin())
|
||||
{
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(srsran::slot_point slot, FormatContext& ctx) -> decltype(std::declval<FormatContext>().out())
|
||||
{
|
||||
return format_to(ctx.out(), "{}/{}", slot.sfn(), slot.slot_idx());
|
||||
}
|
||||
};
|
||||
} // namespace fmt
|
||||
|
||||
namespace srsenb {
|
||||
|
||||
using slot_point = srsran::slot_point;
|
||||
using slot_interval = srsran::slot_interval;
|
||||
|
||||
} // namespace srsenb
|
||||
|
||||
#endif // SRSRAN_SLOT_POINT_H
|
@ -0,0 +1,947 @@
|
||||
/**
|
||||
*
|
||||
* \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);
|
||||
SKIQ_RF_DEBUG("[Tx SOB] ts=%ld\n", h->next_tstamp);
|
||||
} 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,580 @@
|
||||
/**
|
||||
*
|
||||
* \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);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
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);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
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,110 @@
|
||||
/**
|
||||
*
|
||||
* \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)
|
||||
|
||||
#define SKIQ_RF_ERROR(...) \
|
||||
do { \
|
||||
if (rf_skiq_logging_level >= SKIQ_LOG_ERROR) { \
|
||||
fprintf(stdout, "[SKIQ RF ERROR] " __VA_ARGS__); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#endif // SRSLTE_RF_SKIQ_IMP_CFG_H
|
@ -0,0 +1,576 @@
|
||||
/**
|
||||
*
|
||||
* \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;
|
||||
}
|
||||
|
||||
// If the ring buffer read resulted in timeout
|
||||
if (n == SRSRAN_ERROR_TIMEOUT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore blocks with TS=0
|
||||
if (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 > p_tx_block->timestamp) {
|
||||
|
||||
// Get current RF timestamp
|
||||
uint64_t curr_tx_ts = 0UL;
|
||||
skiq_read_curr_tx_timestamp(q->card, q->hdl, &curr_tx_ts);
|
||||
|
||||
// Avoids giving a block to the FPGA that has already passed, otherwise it could hang forever
|
||||
if (curr_tx_ts > p_tx_block->timestamp) {
|
||||
SKIQ_RF_ERROR("[Tx %d:%d block] Tx block (ts=%ld) is in the past (last_tx_ts=%ld, curr_tx_ts=%ld), ignoring\n",
|
||||
q->card,
|
||||
(int)q->hdl,
|
||||
q->p_tx_block->timestamp,
|
||||
last_tx_ts,
|
||||
curr_tx_ts);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
last_tx_ts = 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; last_tx_ts=%ld;\n",
|
||||
q->card,
|
||||
(int)q->hdl,
|
||||
p_tx_block->timestamp,
|
||||
q->block_size,
|
||||
last_tx_ts);
|
||||
|
||||
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 EOB %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;
|
||||
}
|
||||
|
||||
// If no data and no block has started, early return
|
||||
if (buffer == NULL && q->next_offset == 0) {
|
||||
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 Rx 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 Rx 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;
|
||||
}
|
@ -0,0 +1,286 @@
|
||||
/**
|
||||
*
|
||||
* \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 "srsran/phy/io/filesource.h"
|
||||
#include "srsran/phy/phch/ra_nr.h"
|
||||
#include "srsran/phy/ue/ue_dl_nr.h"
|
||||
#include "srsran/phy/utils/debug.h"
|
||||
#include <getopt.h>
|
||||
|
||||
static srsran_carrier_nr_t carrier = {
|
||||
501, // pci
|
||||
0, // absolute_frequency_ssb
|
||||
0, // absolute_frequency_point_a
|
||||
0, // offset_to_carrier
|
||||
srsran_subcarrier_spacing_15kHz, // scs
|
||||
52, // nof_prb
|
||||
0, // start
|
||||
1 // max_mimo_layers
|
||||
};
|
||||
|
||||
static char* filename = NULL;
|
||||
static srsran_pdcch_cfg_nr_t pdcch_cfg = {};
|
||||
static srsran_sch_hl_cfg_nr_t pdsch_hl_cfg = {};
|
||||
static uint16_t rnti = 0x1234;
|
||||
static srsran_rnti_type_t rnti_type = srsran_rnti_type_c;
|
||||
static srsran_slot_cfg_t slot_cfg = {};
|
||||
|
||||
static srsran_softbuffer_rx_t softbuffer = {};
|
||||
static uint8_t* data = NULL;
|
||||
|
||||
static void usage(char* prog)
|
||||
{
|
||||
printf("Usage: %s [pTLR] \n", prog);
|
||||
printf("\t-f File name [Default none]\n");
|
||||
printf("\t-P Number of BWP (Carrier) PRB [Default %d]\n", carrier.nof_prb);
|
||||
printf("\t-i Physical cell identifier [Default %d]\n", carrier.pci);
|
||||
printf("\t-n Slot index [Default %d]\n", slot_cfg.idx);
|
||||
printf("\t-R RNTI in hexadecimal [Default 0x%x]\n", rnti);
|
||||
printf("\t-T RNTI type (c, ra) [Default %s]\n", srsran_rnti_type_str(rnti_type));
|
||||
printf("\t-S Use standard rates [Default %s]\n", srsran_symbol_size_is_standard() ? "yes" : "no");
|
||||
|
||||
printf("\t-v [set srsran_verbose to debug, default none]\n");
|
||||
}
|
||||
|
||||
static int parse_args(int argc, char** argv)
|
||||
{
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "fPivnSRT")) != -1) {
|
||||
switch (opt) {
|
||||
case 'f':
|
||||
filename = argv[optind];
|
||||
break;
|
||||
case 'P':
|
||||
carrier.nof_prb = (uint32_t)strtol(argv[optind], NULL, 10);
|
||||
break;
|
||||
case 'i':
|
||||
carrier.pci = (uint32_t)strtol(argv[optind], NULL, 10);
|
||||
break;
|
||||
case 'v':
|
||||
srsran_verbose++;
|
||||
break;
|
||||
case 'n':
|
||||
slot_cfg.idx = (uint32_t)strtol(argv[optind], NULL, 10);
|
||||
break;
|
||||
case 'R':
|
||||
rnti = (uint16_t)strtol(argv[optind], NULL, 16);
|
||||
break;
|
||||
case 'T':
|
||||
if (strcmp(argv[optind], "c") == 0) {
|
||||
rnti_type = srsran_rnti_type_c;
|
||||
} else if (strcmp(argv[optind], "ra") == 0) {
|
||||
rnti_type = srsran_rnti_type_ra;
|
||||
} else {
|
||||
printf("Invalid RNTI type '%s'\n", argv[optind]);
|
||||
usage(argv[0]);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
break;
|
||||
case 'S':
|
||||
srsran_use_standard_symbol_size(true);
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
static int work_ue_dl(srsran_ue_dl_nr_t* ue_dl, srsran_slot_cfg_t* slot)
|
||||
{
|
||||
// Run FFT
|
||||
srsran_ue_dl_nr_estimate_fft(ue_dl, slot);
|
||||
|
||||
// Blind search
|
||||
srsran_dci_dl_nr_t dci_dl_rx = {};
|
||||
int nof_found_dci = srsran_ue_dl_nr_find_dl_dci(ue_dl, slot, rnti, rnti_type, &dci_dl_rx, 1);
|
||||
if (nof_found_dci < SRSRAN_SUCCESS) {
|
||||
ERROR("Error in blind search");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Print PDCCH blind search candidates
|
||||
for (uint32_t i = 0; i < ue_dl->pdcch_info_count; i++) {
|
||||
const srsran_ue_dl_nr_pdcch_info_t* info = &ue_dl->pdcch_info[i];
|
||||
INFO("PDCCH: rnti=0x%x, crst_id=%d, ss_type=%d, ncce=%d, al=%d, EPRE=%+.2f, RSRP=%+.2f, corr=%.3f; "
|
||||
"nof_bits=%d; crc=%s;",
|
||||
info->dci_ctx.rnti,
|
||||
info->dci_ctx.coreset_id,
|
||||
info->dci_ctx.ss_type,
|
||||
info->dci_ctx.location.ncce,
|
||||
info->dci_ctx.location.L,
|
||||
info->measure.epre_dBfs,
|
||||
info->measure.rsrp_dBfs,
|
||||
info->measure.norm_corr,
|
||||
info->nof_bits,
|
||||
info->result.crc ? "OK" : "KO");
|
||||
}
|
||||
|
||||
if (nof_found_dci < 1) {
|
||||
printf("No DCI found :'(\n");
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
char str[1024] = {};
|
||||
srsran_dci_dl_nr_to_str(&ue_dl->dci, &dci_dl_rx, str, (uint32_t)sizeof(str));
|
||||
printf("Found DCI: %s\n", str);
|
||||
|
||||
// Convert DCI to PDSCH transmission
|
||||
srsran_sch_cfg_nr_t pdsch_cfg = {};
|
||||
if (srsran_ra_dl_dci_to_grant_nr(&carrier, slot, &pdsch_hl_cfg, &dci_dl_rx, &pdsch_cfg, &pdsch_cfg.grant) <
|
||||
SRSRAN_SUCCESS) {
|
||||
ERROR("Error decoding PDSCH search");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
srsran_sch_cfg_nr_info(&pdsch_cfg, str, (uint32_t)sizeof(str));
|
||||
printf("PDSCH: %s\n", str);
|
||||
|
||||
// Set softbuffer
|
||||
pdsch_cfg.grant.tb[0].softbuffer.rx = &softbuffer;
|
||||
|
||||
// Prepare PDSCH result
|
||||
srsran_pdsch_res_nr_t pdsch_res = {};
|
||||
pdsch_res.tb[0].payload = data;
|
||||
|
||||
// Decode PDSCH
|
||||
if (srsran_ue_dl_nr_decode_pdsch(ue_dl, slot, &pdsch_cfg, &pdsch_res) < SRSRAN_SUCCESS) {
|
||||
ERROR("Error decoding PDSCH search");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int ret = SRSRAN_ERROR;
|
||||
srsran_ue_dl_nr_t ue_dl = {};
|
||||
cf_t* buffer[SRSRAN_MAX_PORTS] = {};
|
||||
|
||||
uint32_t sf_len = SRSRAN_SF_LEN_PRB(carrier.nof_prb);
|
||||
buffer[0] = srsran_vec_cf_malloc(sf_len);
|
||||
if (buffer[0] == NULL) {
|
||||
ERROR("Error malloc");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
if (srsran_softbuffer_rx_init_guru(&softbuffer, SRSRAN_SCH_NR_MAX_NOF_CB_LDPC, SRSRAN_LDPC_MAX_LEN_ENCODED_CB) <
|
||||
SRSRAN_SUCCESS) {
|
||||
ERROR("Error init soft-buffer");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
data = srsran_vec_u8_malloc(SRSRAN_SLOT_MAX_NOF_BITS_NR);
|
||||
if (data == NULL) {
|
||||
ERROR("Error malloc");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
srsran_ue_dl_nr_args_t ue_dl_args = {};
|
||||
ue_dl_args.nof_rx_antennas = 1;
|
||||
ue_dl_args.pdsch.sch.disable_simd = false;
|
||||
ue_dl_args.pdsch.sch.decoder_use_flooded = false;
|
||||
ue_dl_args.pdsch.measure_evm = true;
|
||||
ue_dl_args.pdcch.disable_simd = false;
|
||||
ue_dl_args.pdcch.measure_evm = true;
|
||||
ue_dl_args.nof_max_prb = carrier.nof_prb;
|
||||
|
||||
// Set default PDSCH configuration
|
||||
if (parse_args(argc, argv) < SRSRAN_SUCCESS) {
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Check for filename
|
||||
if (filename == NULL) {
|
||||
ERROR("Filename was not provided");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Open filesource
|
||||
srsran_filesource_t filesource = {};
|
||||
if (srsran_filesource_init(&filesource, filename, SRSRAN_COMPLEX_FLOAT_BIN) < SRSRAN_SUCCESS) {
|
||||
ERROR("Error opening filesource");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Configure CORESET
|
||||
srsran_coreset_t* coreset = &pdcch_cfg.coreset[1];
|
||||
pdcch_cfg.coreset_present[1] = true;
|
||||
coreset->duration = 2;
|
||||
for (uint32_t i = 0; i < SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE; i++) {
|
||||
coreset->freq_resources[i] = i < carrier.nof_prb / 6;
|
||||
}
|
||||
|
||||
// Configure Search Space
|
||||
srsran_search_space_t* search_space = &pdcch_cfg.search_space[0];
|
||||
pdcch_cfg.search_space_present[0] = true;
|
||||
search_space->id = 0;
|
||||
search_space->coreset_id = 1;
|
||||
search_space->type = srsran_search_space_type_common_3;
|
||||
search_space->formats[0] = srsran_dci_format_nr_0_0;
|
||||
search_space->formats[1] = srsran_dci_format_nr_1_0;
|
||||
search_space->nof_formats = 2;
|
||||
for (uint32_t L = 0; L < SRSRAN_SEARCH_SPACE_NOF_AGGREGATION_LEVELS_NR; L++) {
|
||||
search_space->nof_candidates[L] = srsran_pdcch_nr_max_candidates_coreset(coreset, L);
|
||||
}
|
||||
|
||||
// Configure RA search space
|
||||
pdcch_cfg.ra_search_space_present = true;
|
||||
pdcch_cfg.ra_search_space = *search_space;
|
||||
pdcch_cfg.ra_search_space.type = srsran_search_space_type_common_1;
|
||||
|
||||
if (srsran_ue_dl_nr_init(&ue_dl, buffer, &ue_dl_args)) {
|
||||
ERROR("Error UE DL");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
if (srsran_ue_dl_nr_set_carrier(&ue_dl, &carrier)) {
|
||||
ERROR("Error setting SCH NR carrier");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Read baseband from file
|
||||
if (srsran_filesource_read(&filesource, buffer[0], (int)ue_dl.fft->sf_sz) < SRSRAN_SUCCESS) {
|
||||
ERROR("Error reading baseband");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
srsran_dci_cfg_nr_t dci_cfg = {};
|
||||
dci_cfg.bwp_dl_initial_bw = carrier.nof_prb;
|
||||
dci_cfg.bwp_ul_initial_bw = carrier.nof_prb;
|
||||
dci_cfg.monitor_common_0_0 = true;
|
||||
if (srsran_ue_dl_nr_set_pdcch_config(&ue_dl, &pdcch_cfg, &dci_cfg)) {
|
||||
ERROR("Error setting CORESET");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Actual decode
|
||||
work_ue_dl(&ue_dl, &slot_cfg);
|
||||
|
||||
ret = SRSRAN_SUCCESS;
|
||||
|
||||
clean_exit:
|
||||
if (buffer[0] != NULL) {
|
||||
free(buffer[0]);
|
||||
}
|
||||
if (data != NULL) {
|
||||
free(data);
|
||||
}
|
||||
srsran_ue_dl_nr_free(&ue_dl);
|
||||
srsran_filesource_free(&filesource);
|
||||
srsran_softbuffer_rx_free(&softbuffer);
|
||||
|
||||
return ret;
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,24 @@
|
||||
/**
|
||||
*
|
||||
* \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 SRSENB_NGAP_INTERFACES_H
|
||||
#define SRSENB_NGAP_INTERFACES_H
|
||||
|
||||
namespace srsenb {
|
||||
class ngap_interface_ngap_proc
|
||||
{
|
||||
public:
|
||||
virtual bool send_initial_ctxt_setup_response() = 0;
|
||||
};
|
||||
} // namespace srsenb
|
||||
|
||||
#endif
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
*
|
||||
* \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 SRSENB_NGAP_UE_H
|
||||
#define SRSENB_NGAP_UE_H
|
||||
|
||||
#include "ngap.h"
|
||||
#include "ngap_ue_proc.h"
|
||||
#include "ngap_ue_utils.h"
|
||||
#include "srsran/asn1/asn1_utils.h"
|
||||
#include "srsran/asn1/ngap.h"
|
||||
namespace srsenb {
|
||||
|
||||
class ngap::ue : public ngap_interface_ngap_proc
|
||||
{
|
||||
public:
|
||||
explicit ue(ngap* ngap_ptr_, rrc_interface_ngap_nr* rrc_ptr_, srslog::basic_logger& logger_);
|
||||
virtual ~ue();
|
||||
// TS 38.413 - Section 9.2.5.1 - Initial UE Message
|
||||
bool send_initial_ue_message(asn1::ngap_nr::rrcestablishment_cause_e cause,
|
||||
srsran::unique_byte_buffer_t pdu,
|
||||
bool has_tmsi,
|
||||
uint32_t s_tmsi = 0);
|
||||
// TS 38.413 - Section 9.2.5.3 - Uplink NAS Transport
|
||||
bool send_ul_nas_transport(srsran::unique_byte_buffer_t pdu);
|
||||
// TS 38.413 - Section 9.2.2.2 - Initial Context Setup Response
|
||||
bool send_initial_ctxt_setup_response();
|
||||
// TS 38.413 - Section 9.2.2.3 - Initial Context Setup Failure
|
||||
bool send_initial_ctxt_setup_failure(asn1::ngap_nr::cause_c cause);
|
||||
// TS 38.413 - Section 9.2.2.1 - Initial Context Setup Request
|
||||
bool handle_initial_ctxt_setup_request(const asn1::ngap_nr::init_context_setup_request_s& msg);
|
||||
// TS 38.413 - Section 9.2.2.5 - UE Context Release Command
|
||||
bool handle_ue_ctxt_release_cmd(const asn1::ngap_nr::ue_context_release_cmd_s& msg);
|
||||
|
||||
bool was_uectxtrelease_requested() const { return release_requested; }
|
||||
void ue_ctxt_setup_complete();
|
||||
void notify_rrc_reconf_complete(const bool reconf_complete_outcome);
|
||||
|
||||
srsran::proc_t<ngap_ue_initial_context_setup_proc> initial_context_setup_proc;
|
||||
srsran::proc_t<ngap_ue_ue_context_release_proc> ue_context_release_proc;
|
||||
|
||||
ngap_ue_ctxt_t ctxt = {};
|
||||
uint16_t stream_id = 1;
|
||||
|
||||
private:
|
||||
// args
|
||||
ngap* ngap_ptr;
|
||||
rrc_interface_ngap_nr* rrc_ptr;
|
||||
|
||||
// state
|
||||
bool release_requested = false;
|
||||
|
||||
// logger
|
||||
srslog::basic_logger& logger;
|
||||
};
|
||||
|
||||
} // namespace srsenb
|
||||
#endif
|
@ -0,0 +1,84 @@
|
||||
/**
|
||||
*
|
||||
* \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 SRSENB_NGAP_UE_PROC_H
|
||||
#define SRSENB_NGAP_UE_PROC_H
|
||||
|
||||
#include "ngap_interfaces.h"
|
||||
#include "ngap_ue_utils.h"
|
||||
|
||||
#include "srsran/asn1/asn1_utils.h"
|
||||
#include "srsran/asn1/ngap.h"
|
||||
#include "srsran/common/buffer_pool.h"
|
||||
#include "srsran/common/stack_procedure.h"
|
||||
#include "srsran/interfaces/gnb_rrc_nr_interfaces.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace srsenb {
|
||||
|
||||
// TS 38.413 - Section 8.3 - UE Context Management Procedures
|
||||
|
||||
// TS 38.413 - Section 8.3.1 - Initial Context Setup
|
||||
class ngap_ue_initial_context_setup_proc
|
||||
{
|
||||
public:
|
||||
explicit ngap_ue_initial_context_setup_proc(ngap_interface_ngap_proc* parent_,
|
||||
rrc_interface_ngap_nr* rrc_,
|
||||
ngap_ue_ctxt_t* ue_ctxt);
|
||||
srsran::proc_outcome_t init(const asn1::ngap_nr::init_context_setup_request_s& msg);
|
||||
srsran::proc_outcome_t react(const bool security_mode_command_outcome);
|
||||
srsran::proc_outcome_t step();
|
||||
static const char* name() { return "Initial Context Setup"; }
|
||||
|
||||
private:
|
||||
ngap_ue_ctxt_t* ue_ctxt;
|
||||
ngap_interface_ngap_proc* parent = nullptr;
|
||||
rrc_interface_ngap_nr* rrc = nullptr;
|
||||
srslog::basic_logger& logger;
|
||||
};
|
||||
|
||||
// TS 38.413 - Section 8.3.2 - UE Context Release Request (NG-RAN node initiated)
|
||||
class ngap_ue_ue_context_release_proc
|
||||
{
|
||||
public:
|
||||
explicit ngap_ue_ue_context_release_proc(ngap_interface_ngap_proc* parent_,
|
||||
rrc_interface_ngap_nr* rrc_,
|
||||
ngap_ue_ctxt_t* ue_ctxt);
|
||||
srsran::proc_outcome_t init(const asn1::ngap_nr::ue_context_release_cmd_s& msg);
|
||||
srsran::proc_outcome_t step();
|
||||
static const char* name() { return "UE Context Release"; }
|
||||
|
||||
private:
|
||||
ngap_ue_ctxt_t* ue_ctxt;
|
||||
ngap_interface_ngap_proc* parent = nullptr;
|
||||
rrc_interface_ngap_nr* rrc = nullptr;
|
||||
srslog::basic_logger& logger;
|
||||
};
|
||||
|
||||
// TS 38.413 - Section 8.3.4 - UE Context Modification
|
||||
class ngap_ue_ue_context_modification_proc
|
||||
{
|
||||
public:
|
||||
explicit ngap_ue_ue_context_modification_proc(ngap_interface_ngap_proc* parent_);
|
||||
srsran::proc_outcome_t init();
|
||||
srsran::proc_outcome_t step();
|
||||
static const char* name() { return "UE Context Modification"; }
|
||||
|
||||
private:
|
||||
ngap_interface_ngap_proc* parent;
|
||||
};
|
||||
|
||||
} // namespace srsenb
|
||||
|
||||
#endif
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
*
|
||||
* \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 SRSENB_NGAP_UE_UTILS_H
|
||||
#define SRSENB_NGAP_UE_UTILS_H
|
||||
|
||||
#include "srsenb/hdr/common/common_enb.h"
|
||||
#include "srsran/adt/optional.h"
|
||||
#include "srsran/common/common.h"
|
||||
#include "srsran/phy/common/phy_common.h"
|
||||
|
||||
namespace srsenb {
|
||||
|
||||
struct ngap_ue_ctxt_t {
|
||||
static const uint32_t invalid_gnb_id = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
uint16_t rnti = SRSRAN_INVALID_RNTI;
|
||||
uint32_t ran_ue_ngap_id = invalid_gnb_id;
|
||||
srsran::optional<uint32_t> amf_ue_ngap_id;
|
||||
uint32_t gnb_cc_idx = 0;
|
||||
struct timeval init_timestamp = {};
|
||||
|
||||
// AMF identifier
|
||||
uint16_t amf_set_id;
|
||||
uint8_t amf_pointer;
|
||||
uint8_t amf_region_id;
|
||||
};
|
||||
|
||||
} // namespace srsenb
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue