You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

628 lines
17 KiB
C++

/**
*
* \section COPYRIGHT
*
* Copyright 2013-2020 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 <pthread.h>
#include <sstream>
#include <string.h>
#include <string>
#include <strings.h>
#include <sys/mman.h>
#include <unistd.h>
#include "srslte/common/log.h"
#include "srslte/common/threads.h"
#include "srslte/srslte.h"
#include "srsue/hdr/phy/phy.h"
#define Error(fmt, ...) \
if (SRSLTE_DEBUG_ENABLED) \
log_h->error(fmt, ##__VA_ARGS__)
#define Warning(fmt, ...) \
if (SRSLTE_DEBUG_ENABLED) \
log_h->warning(fmt, ##__VA_ARGS__)
#define Info(fmt, ...) \
if (SRSLTE_DEBUG_ENABLED) \
log_h->info(fmt, ##__VA_ARGS__)
#define Debug(fmt, ...) \
if (SRSLTE_DEBUG_ENABLED) \
log_h->debug(fmt, ##__VA_ARGS__)
using namespace std;
namespace srsue {
static void srslte_phy_handler(phy_logger_level_t log_level, void* ctx, char* str)
{
phy* r = (phy*)ctx;
r->srslte_phy_logger(log_level, str);
}
void phy::srslte_phy_logger(phy_logger_level_t log_level, char* str)
{
if (log_phy_lib_h) {
switch (log_level) {
case LOG_LEVEL_INFO_S:
log_phy_lib_h->info(" %s", str);
break;
case LOG_LEVEL_DEBUG_S:
log_phy_lib_h->debug(" %s", str);
break;
case LOG_LEVEL_ERROR_S:
log_phy_lib_h->error(" %s", str);
break;
default:
break;
}
} else {
printf("[PHY_LIB]: %s\n", str);
}
}
void phy::set_default_args(phy_args_t& args_)
{
args_.nof_rx_ant = 1;
args_.ul_pwr_ctrl_en = false;
args_.prach_gain = -1;
args_.cqi_max = -1;
args_.cqi_fixed = -1;
args_.snr_ema_coeff = 0.1;
args_.snr_estim_alg = "refs";
args_.pdsch_max_its = 4;
args_.nof_phy_threads = DEFAULT_WORKERS;
args_.equalizer_mode = "mmse";
args_.cfo_integer_enabled = false;
args_.cfo_correct_tol_hz = 50;
args_.sss_algorithm = "full";
args_.estimator_fil_auto = false;
args_.estimator_fil_stddev = 1.0f;
args_.estimator_fil_order = 4;
}
bool phy::check_args(const phy_args_t& args_)
{
if (args_.nof_phy_threads > MAX_WORKERS) {
srslte::console("Error in PHY args: nof_phy_threads must be 1, 2 or 3\n");
return false;
}
if (args_.snr_ema_coeff > 1.0) {
srslte::console("Error in PHY args: snr_ema_coeff must be 0<=w<=1\n");
return false;
}
return true;
}
int phy::init(const phy_args_t& args_, stack_interface_phy_lte* stack_, srslte::radio_interface_phy* radio_)
{
stack = stack_;
radio = radio_;
init(args_);
return SRSLTE_SUCCESS;
}
int phy::init(const phy_args_t& args_)
{
std::unique_lock<std::mutex> lock(config_mutex);
mlockall(MCL_CURRENT | MCL_FUTURE);
args = args_;
// Force frequency if given as argument
if (args.dl_freq > 0 && args.ul_freq > 0) {
sfsync.force_freq(args.dl_freq, args.ul_freq);
}
// Add PHY lib log
if (srslte::log::get_level_from_string(args.log.phy_lib_level) != srslte::LOG_LEVEL_NONE) {
log_phy_lib_h = std::unique_ptr<srslte::log_filter>(new srslte::log_filter);
log_phy_lib_h->init("PHY_LIB", logger, true);
log_phy_lib_h->set_level(args.log.phy_lib_level);
log_phy_lib_h->set_hex_limit(args.log.phy_hex_limit);
srslte_phy_log_register_handler(this, srslte_phy_handler);
}
// set default logger
{
log_h = std::unique_ptr<srslte::log_filter>(new srslte::log_filter);
log_h->init("PHY", logger, true);
log_h->set_level(args.log.phy_level);
log_h->set_hex_limit(args.log.phy_hex_limit);
}
if (!check_args(args)) {
return false;
}
is_configured = false;
start();
return true;
}
// Initializes PHY in a thread
void phy::run_thread()
{
std::unique_lock<std::mutex> lock(config_mutex);
prach_buffer.init(SRSLTE_MAX_PRB, log_h.get());
common.init(&args, log_h.get(), radio, stack, &sfsync);
// Initialise workers
lte_workers.init(&common, logger, WORKERS_THREAD_PRIO);
nr_workers.init(&common, logger, WORKERS_THREAD_PRIO);
// Warning this must be initialized after all workers have been added to the pool
sfsync.init(radio,
stack,
&prach_buffer,
&lte_workers,
&nr_workers,
&common,
log_h.get(),
log_phy_lib_h.get(),
SF_RECV_THREAD_PRIO,
args.sync_cpu_affinity);
// Disable UL signal pregeneration until the attachment
enable_pregen_signals(false);
is_configured = true;
config_cond.notify_all();
}
void phy::wait_initialize()
{
// wait until PHY is configured
std::unique_lock<std::mutex> lock(config_mutex);
while (!is_configured) {
config_cond.wait(lock);
}
}
bool phy::is_initiated()
{
return is_configured;
}
void phy::stop()
{
std::unique_lock<std::mutex> lock(config_mutex);
cmd_worker.stop();
cmd_worker_cell.stop();
if (is_configured) {
sfsync.stop();
lte_workers.stop();
nr_workers.stop();
prach_buffer.stop();
wait_thread_finish();
is_configured = false;
}
}
void phy::get_metrics(phy_metrics_t* m)
{
uint32_t dl_earfcn = 0;
srslte_cell_t cell = {};
sfsync.get_current_cell(&cell, &dl_earfcn);
m->info[0].pci = cell.id;
m->info[0].dl_earfcn = dl_earfcn;
for (uint32_t i = 1; i < args.nof_lte_carriers; i++) {
m->info[i].dl_earfcn = common.cell_state.get_earfcn(i);
m->info[i].pci = common.cell_state.get_pci(i);
}
common.get_ch_metrics(m->ch);
common.get_dl_metrics(m->dl);
common.get_ul_metrics(m->ul);
common.get_sync_metrics(m->sync);
m->nof_active_cc = args.nof_lte_carriers;
}
void phy::set_timeadv_rar(uint32_t ta_cmd)
{
common.ta.add_ta_cmd_rar(ta_cmd);
}
void phy::set_timeadv(uint32_t ta_cmd)
{
common.ta.add_ta_cmd_new(ta_cmd);
}
void phy::deactivate_scells()
{
common.cell_state.deactivate_all();
}
void phy::set_activation_deactivation_scell(uint32_t cmd, uint32_t tti)
{
common.cell_state.set_activation_deactivation(cmd, tti);
}
void phy::configure_prach_params()
{
Debug("Configuring PRACH parameters\n");
prach_cfg.tdd_config = tdd_config;
if (!prach_buffer.set_cell(selected_cell, prach_cfg)) {
Error("Configuring PRACH parameters\n");
}
}
void phy::set_cells_to_meas(uint32_t earfcn, const std::set<uint32_t>& pci)
{
// Check if the EARFCN matches with serving cell
uint32_t pcell_earfcn = 0;
sfsync.get_current_cell(nullptr, &pcell_earfcn);
bool available = (pcell_earfcn == earfcn);
// Find if there is secondary serving cell configured with the specified EARFCN
uint32_t cc_empty = 0;
for (uint32_t cc = 1; cc < args.nof_lte_carriers and not available; cc++) {
// If it is configured...
if (common.cell_state.is_configured(cc)) {
// ... Check if the EARFCN match
if (common.cell_state.get_earfcn(cc) == earfcn) {
available = true;
}
} else if (cc_empty == 0) {
// ... otherwise, save the CC as non-configured
cc_empty = cc;
}
}
// If not available and a non-configured carrier is available, configure it.
if (not available and cc_empty != 0) {
// Copy all attributes from serving cell
srslte_cell_t cell = selected_cell;
// Select the first PCI in the list
if (not pci.empty()) {
cell.id = *pci.begin();
}
// Configure a the empty carrier as it was CA
set_scell(cell, cc_empty, earfcn);
}
// Finally, set the serving cell measure
sfsync.set_cells_to_meas(earfcn, pci);
}
void phy::meas_stop()
{
sfsync.meas_stop();
}
// This function executes one part of the procedure immediatly and returns to continue in the background.
// When it returns, the caller thread can expect the PHY to have switched to IDLE and have stopped all DL/UL/PRACH
// processing.
bool phy::cell_select(phy_cell_t cell)
{
sfsync.scell_sync_stop();
if (sfsync.cell_select_init(cell)) {
reset();
// Update PCI before starting the background command to make sure PRACH gets the updated value
selected_cell.id = cell.pci;
cmd_worker_cell.add_cmd([this, cell]() {
bool ret = sfsync.cell_select_start(cell);
if (ret) {
srslte_cell_t sync_cell;
sfsync.get_current_cell(&sync_cell);
selected_cell = sync_cell;
}
stack->cell_select_complete(ret);
});
return true;
} else {
log_h->warning("Could not start Cell Selection procedure\n");
return false;
}
}
// This function executes one part of the procedure immediatly and returns to continue in the background.
// When it returns, the caller thread can expect the PHY to have switched to IDLE and have stopped all DL/UL/PRACH
// processing.
bool phy::cell_search()
{
sfsync.scell_sync_stop();
if (sfsync.cell_search_init()) {
reset();
cmd_worker_cell.add_cmd([this]() {
phy_cell_t found_cell = {};
rrc_interface_phy_lte::cell_search_ret_t ret = sfsync.cell_search_start(&found_cell);
stack->cell_search_complete(ret, found_cell);
});
} else {
log_h->warning("Could not start Cell Search procedure\n");
}
return true;
}
bool phy::cell_is_camping()
{
return sfsync.cell_is_camping();
}
float phy::get_phr()
{
float phr = radio->get_info()->max_tx_gain - common.get_pusch_power();
return phr;
}
float phy::get_pathloss_db()
{
return common.get_pathloss();
}
void phy::prach_send(uint32_t preamble_idx, int allowed_subframe, float target_power_dbm, float ta_base_sec)
{
common.ta.set_base_sec(ta_base_sec);
common.reset_radio();
if (!prach_buffer.prepare_to_send(preamble_idx, allowed_subframe, target_power_dbm)) {
Error("Preparing PRACH to send\n");
}
}
phy_interface_mac_lte::prach_info_t phy::prach_get_info()
{
return prach_buffer.get_info();
}
// Handle the case of a radio overflow. Resynchronise immediatly
void phy::radio_overflow()
{
sfsync.radio_overflow();
}
void phy::radio_failure()
{
// TODO: handle failure
Error("Radio failure.\n");
}
void phy::reset()
{
Info("Resetting PHY...\n");
common.ta.set_base_sec(0);
common.reset();
}
uint32_t phy::get_current_tti()
{
return sfsync.get_current_tti();
}
void phy::sr_send()
{
common.sr_enabled = true;
common.sr_last_tx_tti = -1;
Debug("sr_send(): sr_enabled=%d, last_tx_tti=%d\n", common.sr_enabled, common.sr_last_tx_tti);
}
int phy::sr_last_tx_tti()
{
return common.sr_last_tx_tti;
}
void phy::set_rar_grant(uint8_t grant_payload[SRSLTE_RAR_GRANT_LEN], uint16_t rnti)
{
common.set_rar_grant(grant_payload, rnti, tdd_config);
}
void phy::set_crnti(uint16_t rnti)
{
// set_crnti() is an operation that takes time, run in background worker
cmd_worker.add_cmd([this, rnti]() {
log_h->info("Configuring sequences for C-RNTI=0x%x...\n", rnti);
for (uint32_t i = 0; i < args.nof_phy_threads; i++) {
// set_crnti is not protected so run when worker is finished
lte::sf_worker* w = lte_workers.wait_worker_id(i);
if (w) {
w->set_crnti_unlocked(rnti);
w->release();
}
}
log_h->info("Finished configuring sequences for C-RNTI=0x%x.\n", rnti);
});
}
// Start GUI
void phy::start_plot()
{
lte_workers[0]->start_plot();
if (args.nof_nr_carriers > 0) {
nr_workers[0]->start_plot();
}
}
void phy::enable_pregen_signals(bool enable)
{
for (uint32_t i = 0; i < args.nof_phy_threads; i++) {
lte_workers[i]->enable_pregen_signals_unlocked(enable);
}
}
bool phy::set_config(srslte::phy_cfg_t config_, uint32_t cc_idx)
{
if (!is_initiated()) {
fprintf(stderr, "Error calling set_config(): PHY not initialized\n");
return false;
}
// Check parameters are valid
if (cc_idx >= args.nof_lte_carriers) {
srslte::console("Received SCell configuration for index %d but there are not enough CC workers available\n",
cc_idx);
return false;
}
Info("Setting configuration\n");
// The PRACH configuration shall be updated only if:
// - The new configuration belongs to the primary cell
// - The PRACH configuration is present
if (!cc_idx && config_.prach_cfg_present) {
prach_cfg = config_.prach_cfg;
}
// Apply configuration after the worker is finished to avoid race conditions
cmd_worker.add_cmd([this, config_, cc_idx]() {
log_h->info("Setting new PHY configuration cc_idx=%d...\n", cc_idx);
for (uint32_t i = 0; i < args.nof_phy_threads; i++) {
// set_cell is not protected so run when worker is finished
lte::sf_worker* w = lte_workers.wait_worker_id(i);
if (w) {
w->set_config_unlocked(cc_idx, config_);
w->release();
}
}
log_h->info("Finished setting new PHY configuration cc_idx=%d\n", cc_idx);
// It is up to the PRACH component to detect whether the cell or the configuration have changed to reconfigure
configure_prach_params();
stack->set_config_complete(true);
});
return true;
}
bool phy::set_scell(srslte_cell_t cell_info, uint32_t cc_idx, uint32_t earfcn)
{
if (!is_initiated()) {
fprintf(stderr, "Error calling set_config(): PHY not initialized\n");
return false;
}
if (cc_idx == 0) {
log_h->error("Received SCell configuration for invalid cc_idx=0\n");
return false;
}
// Check parameters are valid
if (cc_idx >= args.nof_lte_carriers) {
srslte::console("Received SCell configuration for index %d but there are not enough CC workers available\n",
cc_idx);
return false;
}
// First of all check validity of parameters
if (!srslte_cell_isvalid(&cell_info)) {
log_h->error("Received SCell configuration for an invalid cell\n");
return false;
}
bool earfcn_is_different = common.cell_state.get_earfcn(cc_idx) != earfcn;
// Set inter-frequency measurement
sfsync.set_inter_frequency_measurement(cc_idx, earfcn, cell_info);
// Store secondary serving cell EARFCN and PCI
common.cell_state.configure(cc_idx, earfcn, cell_info.id);
// Reset cell configuration
for (uint32_t i = 0; i < args.nof_phy_threads; i++) {
lte_workers[i]->reset_cell_unlocked(cc_idx);
}
// Component carrier index zero should be reserved for PCell
// Send configuration to workers
cmd_worker.add_cmd([this, cell_info, cc_idx, earfcn, earfcn_is_different]() {
log_h->info("Setting new SCell configuration cc_idx=%d, earfcn=%d...\n", cc_idx, earfcn);
for (uint32_t i = 0; i < args.nof_phy_threads; i++) {
// set_cell is not protected so run when worker is finished
lte::sf_worker* w = lte_workers.wait_worker_id(i);
if (w) {
w->set_cell_unlocked(cc_idx, cell_info);
w->release();
}
}
// Change frequency only if the earfcn was modified
if (earfcn_is_different) {
double dl_freq = srslte_band_fd(earfcn) * 1e6;
double ul_freq = srslte_band_fu(common.get_ul_earfcn(earfcn)) * 1e6;
radio->set_rx_freq(cc_idx, dl_freq);
radio->set_tx_freq(cc_idx, ul_freq);
}
// Set secondary serving cell synchronization
sfsync.scell_sync_set(cc_idx, cell_info);
log_h->info("Finished setting new SCell configuration cc_idx=%d, earfcn=%d\n", cc_idx, earfcn);
stack->set_scell_complete(true);
});
return true;
}
void phy::set_config_tdd(srslte_tdd_config_t& tdd_config_)
{
tdd_config = tdd_config_;
if (!tdd_config.configured) {
srslte::console("Setting TDD-config: %d, SS config: %d\n", tdd_config.sf_config, tdd_config.ss_config);
}
tdd_config.configured = true;
// Apply config when worker is finished
cmd_worker.add_cmd([this]() {
for (uint32_t i = 0; i < args.nof_phy_threads; i++) {
// set_tdd_config is not protected so run when worker is finished
lte::sf_worker* w = lte_workers.wait_worker_id(i);
if (w) {
w->set_tdd_config_unlocked(tdd_config);
w->release();
}
}
});
}
void phy::set_config_mbsfn_sib2(srslte::mbsfn_sf_cfg_t* cfg_list, uint32_t nof_cfgs)
{
if (nof_cfgs > 1) {
Warning("SIB2 has %d MBSFN subframe configs - only 1 supported\n", nof_cfgs);
}
if (nof_cfgs > 0) {
common.mbsfn_config.mbsfn_subfr_cnfg = cfg_list[0];
common.build_mch_table();
}
}
void phy::set_config_mbsfn_sib13(const srslte::sib13_t& sib13)
{
common.mbsfn_config.mbsfn_notification_cnfg = sib13.notif_cfg;
if (sib13.nof_mbsfn_area_info > 1) {
Warning("SIB13 has %d MBSFN area info elements - only 1 supported\n", sib13.nof_mbsfn_area_info);
}
if (sib13.nof_mbsfn_area_info > 0) {
common.mbsfn_config.mbsfn_area_info = sib13.mbsfn_area_info_list[0];
common.build_mcch_table();
}
}
void phy::set_config_mbsfn_mcch(const srslte::mcch_msg_t& mcch)
{
common.mbsfn_config.mcch = mcch;
stack->set_mbsfn_config(common.mbsfn_config.mcch.pmch_info_list[0].nof_mbms_session_info);
common.set_mch_period_stop(common.mbsfn_config.mcch.pmch_info_list[0].sf_alloc_end);
common.set_mcch();
}
void phy::set_mch_period_stop(uint32_t stop)
{
common.set_mch_period_stop(stop);
}
} // namespace srsue