532 lines
16 KiB
C++
532 lines
16 KiB
C++
/*
|
|
* Copyright 2013-2020 Software Radio Systems Limited
|
|
*
|
|
* This file is part of srsLTE.
|
|
*
|
|
* srsLTE is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* srsLTE is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* A copy of the GNU Affero General Public License can be found in
|
|
* the LICENSE file in the top-level directory of this distribution
|
|
* and at http://www.gnu.org/licenses/.
|
|
*
|
|
*/
|
|
|
|
#include <srsenb/hdr/stack/mac/scheduler_ue.h>
|
|
#include <string.h>
|
|
|
|
#include "srsenb/hdr/stack/mac/scheduler.h"
|
|
#include "srsenb/hdr/stack/mac/scheduler_carrier.h"
|
|
#include "srslte/common/logmap.h"
|
|
#include "srslte/srslte.h"
|
|
|
|
#define Console(fmt, ...) srslte::out_stream(fmt, ##__VA_ARGS__)
|
|
#define Error(fmt, ...) srslte::logmap::get("MAC ")->error(fmt, ##__VA_ARGS__)
|
|
|
|
namespace srsenb {
|
|
|
|
namespace sched_utils {
|
|
|
|
uint32_t tti_subtract(uint32_t tti1, uint32_t tti2)
|
|
{
|
|
return TTI_SUB(tti1, tti2);
|
|
}
|
|
|
|
uint32_t max_tti(uint32_t tti1, uint32_t tti2)
|
|
{
|
|
return ((tti1 - tti2) > 10240 / 2) ? SRSLTE_MIN(tti1, tti2) : SRSLTE_MAX(tti1, tti2);
|
|
}
|
|
|
|
} // namespace sched_utils
|
|
|
|
/*******************************************************
|
|
* Sched Params
|
|
*******************************************************/
|
|
|
|
bool sched_cell_params_t::set_cfg(uint32_t enb_cc_idx_,
|
|
const sched_interface::cell_cfg_t& cfg_,
|
|
const sched_interface::sched_args_t& sched_args)
|
|
{
|
|
enb_cc_idx = enb_cc_idx_;
|
|
cfg = cfg_;
|
|
sched_cfg = &sched_args;
|
|
|
|
// Basic cell config checks
|
|
if (cfg.si_window_ms == 0) {
|
|
Error("SCHED: Invalid si-window length 0 ms\n");
|
|
return false;
|
|
}
|
|
|
|
bool invalid_prach;
|
|
if (cfg.cell.nof_prb == 6) {
|
|
// PUCCH has to allow space for Msg3
|
|
if (cfg.nrb_pucch > 1) {
|
|
Console("Invalid PUCCH configuration: nrb_pucch=%d does not allow space for Msg3 transmission..\n",
|
|
cfg.nrb_pucch);
|
|
return false;
|
|
}
|
|
// PRACH has to fit within the PUSCH+PUCCH space
|
|
invalid_prach = cfg.prach_freq_offset + 6 > cfg.cell.nof_prb;
|
|
} else {
|
|
// PRACH has to fit within the PUSCH space
|
|
invalid_prach = (cfg.prach_freq_offset + 6) > (cfg.cell.nof_prb - cfg.nrb_pucch) or
|
|
((int)cfg.prach_freq_offset < cfg.nrb_pucch);
|
|
}
|
|
if (invalid_prach) {
|
|
Error("Invalid PRACH configuration: frequency offset=%d outside bandwidth limits\n", cfg.prach_freq_offset);
|
|
Console("Invalid PRACH configuration: frequency offset=%d outside bandwidth limits\n", cfg.prach_freq_offset);
|
|
return false;
|
|
}
|
|
|
|
// Set derived sched parameters
|
|
|
|
// init regs
|
|
regs.reset(new srslte_regs_t{});
|
|
if (srslte_regs_init(regs.get(), cfg.cell) != SRSLTE_SUCCESS) {
|
|
Error("Getting DCI locations\n");
|
|
return false;
|
|
}
|
|
|
|
// Compute Common locations for DCI for each CFI
|
|
for (uint32_t cfix = 0; cfix < pdcch_grid_t::MAX_CFI; cfix++) {
|
|
sched_utils::generate_cce_location(regs.get(), &common_locations[cfix], cfix + 1);
|
|
}
|
|
if (common_locations[sched_cfg->max_nof_ctrl_symbols - 1].nof_loc[2] == 0) {
|
|
Error("SCHED: Current cfi=%d is not valid for broadcast (check scheduler.max_nof_ctrl_symbols in conf file).\n",
|
|
sched_cfg->max_nof_ctrl_symbols);
|
|
Console("SCHED: Current cfi=%d is not valid for broadcast (check scheduler.max_nof_ctrl_symbols in conf file).\n",
|
|
sched_cfg->max_nof_ctrl_symbols);
|
|
return false;
|
|
}
|
|
|
|
// Compute UE locations for RA-RNTI
|
|
for (uint32_t cfi = 0; cfi < 3; cfi++) {
|
|
for (uint32_t sf_idx = 0; sf_idx < 10; sf_idx++) {
|
|
sched_utils::generate_cce_location(regs.get(), &rar_locations[cfi][sf_idx], cfi + 1, sf_idx);
|
|
}
|
|
}
|
|
|
|
// precompute nof cces in PDCCH for each CFI
|
|
for (uint32_t cfix = 0; cfix < nof_cce_table.size(); ++cfix) {
|
|
int ret = srslte_regs_pdcch_ncce(regs.get(), cfix + 1);
|
|
if (ret < 0) {
|
|
Error("SCHED: Failed to calculate the number of CCEs in the PDCCH\n");
|
|
return false;
|
|
}
|
|
nof_cce_table[cfix] = (uint32_t)ret;
|
|
}
|
|
|
|
P = srslte_ra_type0_P(cfg.cell.nof_prb);
|
|
nof_rbgs = srslte::ceil_div(cfg.cell.nof_prb, P);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*******************************************************
|
|
*
|
|
* Initialization and sched configuration functions
|
|
*
|
|
*******************************************************/
|
|
|
|
sched::sched() : log_h(srslte::logmap::get("MAC")) {}
|
|
|
|
sched::~sched() {}
|
|
|
|
void sched::init(rrc_interface_mac* rrc_)
|
|
{
|
|
rrc = rrc_;
|
|
|
|
// Initialize first carrier scheduler
|
|
carrier_schedulers.emplace_back(new carrier_sched{rrc, &ue_db, 0, &sched_results});
|
|
|
|
reset();
|
|
}
|
|
|
|
int sched::reset()
|
|
{
|
|
std::lock_guard<std::mutex> lock(sched_mutex);
|
|
configured = false;
|
|
for (std::unique_ptr<carrier_sched>& c : carrier_schedulers) {
|
|
c->reset();
|
|
}
|
|
ue_db.clear();
|
|
return 0;
|
|
}
|
|
|
|
void sched::set_sched_cfg(sched_interface::sched_args_t* sched_cfg_)
|
|
{
|
|
std::lock_guard<std::mutex> lock(sched_mutex);
|
|
if (sched_cfg_ != nullptr) {
|
|
sched_cfg = *sched_cfg_;
|
|
}
|
|
}
|
|
|
|
int sched::cell_cfg(const std::vector<sched_interface::cell_cfg_t>& cell_cfg)
|
|
{
|
|
std::lock_guard<std::mutex> lock(sched_mutex);
|
|
// Setup derived config params
|
|
sched_cell_params.resize(cell_cfg.size());
|
|
for (uint32_t cc_idx = 0; cc_idx < cell_cfg.size(); ++cc_idx) {
|
|
if (not sched_cell_params[cc_idx].set_cfg(cc_idx, cell_cfg[cc_idx], sched_cfg)) {
|
|
return SRSLTE_ERROR;
|
|
}
|
|
}
|
|
|
|
// Create remaining cells, if not created yet
|
|
uint32_t prev_size = carrier_schedulers.size();
|
|
carrier_schedulers.resize(sched_cell_params.size());
|
|
for (uint32_t i = prev_size; i < sched_cell_params.size(); ++i) {
|
|
carrier_schedulers[i].reset(new carrier_sched{rrc, &ue_db, i, &sched_results});
|
|
}
|
|
|
|
// setup all carriers cfg params
|
|
for (uint32_t i = 0; i < sched_cell_params.size(); ++i) {
|
|
carrier_schedulers[i]->carrier_cfg(sched_cell_params[i]);
|
|
}
|
|
|
|
configured = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*******************************************************
|
|
*
|
|
* FAPI-like main sched interface. Wrappers to UE object
|
|
*
|
|
*******************************************************/
|
|
|
|
int sched::ue_cfg(uint16_t rnti, const sched_interface::ue_cfg_t& ue_cfg)
|
|
{
|
|
std::lock_guard<std::mutex> lock(sched_mutex);
|
|
// Add or config user
|
|
auto it = ue_db.find(rnti);
|
|
if (it == ue_db.end()) {
|
|
// create new user
|
|
ue_db[rnti].init(rnti, sched_cell_params);
|
|
it = ue_db.find(rnti);
|
|
}
|
|
it->second.set_cfg(ue_cfg);
|
|
|
|
return SRSLTE_SUCCESS;
|
|
}
|
|
|
|
int sched::ue_rem(uint16_t rnti)
|
|
{
|
|
std::lock_guard<std::mutex> lock(sched_mutex);
|
|
if (ue_db.count(rnti) > 0) {
|
|
ue_db.erase(rnti);
|
|
} else {
|
|
Error("User rnti=0x%x not found\n", rnti);
|
|
return SRSLTE_ERROR;
|
|
}
|
|
return SRSLTE_SUCCESS;
|
|
}
|
|
|
|
bool sched::ue_exists(uint16_t rnti)
|
|
{
|
|
return ue_db_access(rnti, [](sched_ue& ue) {}) >= 0;
|
|
}
|
|
|
|
void sched::phy_config_enabled(uint16_t rnti, bool enabled)
|
|
{
|
|
// TODO: Check if correct use of last_tti
|
|
ue_db_access(
|
|
rnti, [this, enabled](sched_ue& ue) { ue.phy_config_enabled(last_tti, enabled); }, __PRETTY_FUNCTION__);
|
|
}
|
|
|
|
int sched::bearer_ue_cfg(uint16_t rnti, uint32_t lc_id, sched_interface::ue_bearer_cfg_t* cfg_)
|
|
{
|
|
return ue_db_access(rnti, [lc_id, cfg_](sched_ue& ue) { ue.set_bearer_cfg(lc_id, cfg_); });
|
|
}
|
|
|
|
int sched::bearer_ue_rem(uint16_t rnti, uint32_t lc_id)
|
|
{
|
|
return ue_db_access(rnti, [lc_id](sched_ue& ue) { ue.rem_bearer(lc_id); });
|
|
}
|
|
|
|
uint32_t sched::get_dl_buffer(uint16_t rnti)
|
|
{
|
|
// TODO: Check if correct use of last_tti
|
|
uint32_t ret = SRSLTE_ERROR;
|
|
ue_db_access(
|
|
rnti, [&ret](sched_ue& ue) { ret = ue.get_pending_dl_new_data(); }, __PRETTY_FUNCTION__);
|
|
return ret;
|
|
}
|
|
|
|
uint32_t sched::get_ul_buffer(uint16_t rnti)
|
|
{
|
|
// TODO: Check if correct use of last_tti
|
|
uint32_t ret = SRSLTE_ERROR;
|
|
ue_db_access(
|
|
rnti, [this, &ret](sched_ue& ue) { ret = ue.get_pending_ul_new_data(last_tti); }, __PRETTY_FUNCTION__);
|
|
return ret;
|
|
}
|
|
|
|
int sched::dl_rlc_buffer_state(uint16_t rnti, uint32_t lc_id, uint32_t tx_queue, uint32_t retx_queue)
|
|
{
|
|
return ue_db_access(rnti, [&](sched_ue& ue) { ue.dl_buffer_state(lc_id, tx_queue, retx_queue); });
|
|
}
|
|
|
|
int sched::dl_mac_buffer_state(uint16_t rnti, uint32_t ce_code, uint32_t nof_cmds)
|
|
{
|
|
return ue_db_access(rnti, [ce_code, nof_cmds](sched_ue& ue) { ue.mac_buffer_state(ce_code, nof_cmds); });
|
|
}
|
|
|
|
int sched::dl_ack_info(uint32_t tti, uint16_t rnti, uint32_t enb_cc_idx, uint32_t tb_idx, bool ack)
|
|
{
|
|
int ret = -1;
|
|
ue_db_access(
|
|
rnti, [&](sched_ue& ue) { ret = ue.set_ack_info(tti, enb_cc_idx, tb_idx, ack); }, __PRETTY_FUNCTION__);
|
|
return ret;
|
|
}
|
|
|
|
int sched::ul_crc_info(uint32_t tti_rx, uint16_t rnti, uint32_t enb_cc_idx, bool crc)
|
|
{
|
|
return ue_db_access(
|
|
rnti, [tti_rx, enb_cc_idx, crc](sched_ue& ue) { ue.set_ul_crc(srslte::tti_point{tti_rx}, enb_cc_idx, crc); });
|
|
}
|
|
|
|
int sched::dl_ri_info(uint32_t tti, uint16_t rnti, uint32_t enb_cc_idx, uint32_t ri_value)
|
|
{
|
|
return ue_db_access(rnti, [tti, enb_cc_idx, ri_value](sched_ue& ue) { ue.set_dl_ri(tti, enb_cc_idx, ri_value); });
|
|
}
|
|
|
|
int sched::dl_pmi_info(uint32_t tti, uint16_t rnti, uint32_t enb_cc_idx, uint32_t pmi_value)
|
|
{
|
|
return ue_db_access(rnti, [tti, enb_cc_idx, pmi_value](sched_ue& ue) { ue.set_dl_pmi(tti, enb_cc_idx, pmi_value); });
|
|
}
|
|
|
|
int sched::dl_cqi_info(uint32_t tti, uint16_t rnti, uint32_t enb_cc_idx, uint32_t cqi_value)
|
|
{
|
|
return ue_db_access(rnti, [tti, enb_cc_idx, cqi_value](sched_ue& ue) { ue.set_dl_cqi(tti, enb_cc_idx, cqi_value); });
|
|
}
|
|
|
|
int sched::dl_rach_info(uint32_t enb_cc_idx, dl_sched_rar_info_t rar_info)
|
|
{
|
|
std::lock_guard<std::mutex> lock(sched_mutex);
|
|
return carrier_schedulers[enb_cc_idx]->dl_rach_info(rar_info);
|
|
}
|
|
|
|
int sched::ul_cqi_info(uint32_t tti, uint16_t rnti, uint32_t enb_cc_idx, uint32_t cqi, uint32_t ul_ch_code)
|
|
{
|
|
return ue_db_access(rnti, [&](sched_ue& ue) { ue.set_ul_cqi(tti, enb_cc_idx, cqi, ul_ch_code); });
|
|
}
|
|
|
|
int sched::ul_bsr(uint16_t rnti, uint32_t lcg_id, uint32_t bsr)
|
|
{
|
|
return ue_db_access(rnti, [lcg_id, bsr](sched_ue& ue) { ue.ul_buffer_state(lcg_id, bsr); });
|
|
}
|
|
|
|
int sched::ul_buffer_add(uint16_t rnti, uint32_t lcid, uint32_t bytes)
|
|
{
|
|
return ue_db_access(rnti, [lcid, bytes](sched_ue& ue) { ue.ul_buffer_add(lcid, bytes); });
|
|
}
|
|
|
|
int sched::ul_phr(uint16_t rnti, int phr)
|
|
{
|
|
return ue_db_access(
|
|
rnti, [phr](sched_ue& ue) { ue.ul_phr(phr); }, __PRETTY_FUNCTION__);
|
|
}
|
|
|
|
int sched::ul_sr_info(uint32_t tti, uint16_t rnti)
|
|
{
|
|
return ue_db_access(
|
|
rnti, [](sched_ue& ue) { ue.set_sr(); }, __PRETTY_FUNCTION__);
|
|
}
|
|
|
|
void sched::set_dl_tti_mask(uint8_t* tti_mask, uint32_t nof_sfs)
|
|
{
|
|
std::lock_guard<std::mutex> lock(sched_mutex);
|
|
carrier_schedulers[0]->set_dl_tti_mask(tti_mask, nof_sfs);
|
|
}
|
|
|
|
void sched::tpc_inc(uint16_t rnti)
|
|
{
|
|
ue_db_access(
|
|
rnti, [](sched_ue& ue) { ue.tpc_inc(); }, __PRETTY_FUNCTION__);
|
|
}
|
|
|
|
void sched::tpc_dec(uint16_t rnti)
|
|
{
|
|
ue_db_access(
|
|
rnti, [](sched_ue& ue) { ue.tpc_dec(); }, __PRETTY_FUNCTION__);
|
|
}
|
|
|
|
std::array<int, SRSLTE_MAX_CARRIERS> sched::get_enb_ue_cc_map(uint16_t rnti)
|
|
{
|
|
std::array<int, SRSLTE_MAX_CARRIERS> ret{};
|
|
ret.fill(-1); // -1 for inactive carriers
|
|
ue_db_access(
|
|
rnti,
|
|
[this, &ret](sched_ue& ue) {
|
|
for (size_t enb_cc_idx = 0; enb_cc_idx < carrier_schedulers.size(); ++enb_cc_idx) {
|
|
auto p = ue.get_cell_index(enb_cc_idx);
|
|
if (p.second < SRSLTE_MAX_CARRIERS) {
|
|
ret[enb_cc_idx] = p.second;
|
|
}
|
|
}
|
|
},
|
|
__PRETTY_FUNCTION__);
|
|
return ret;
|
|
}
|
|
|
|
/*******************************************************
|
|
*
|
|
* Main sched functions
|
|
*
|
|
*******************************************************/
|
|
|
|
// Downlink Scheduler API
|
|
int sched::dl_sched(uint32_t tti_tx_dl, uint32_t cc_idx, sched_interface::dl_sched_res_t& sched_result)
|
|
{
|
|
if (!configured) {
|
|
return 0;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(sched_mutex);
|
|
uint32_t tti_rx = sched_utils::tti_subtract(tti_tx_dl, FDD_HARQ_DELAY_UL_MS);
|
|
last_tti = sched_utils::max_tti(last_tti, tti_rx);
|
|
|
|
if (cc_idx < carrier_schedulers.size()) {
|
|
// Compute scheduling Result for tti_rx
|
|
const cc_sched_result& tti_sched = carrier_schedulers[cc_idx]->generate_tti_result(tti_rx);
|
|
|
|
// copy result
|
|
sched_result = tti_sched.dl_sched_result;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Uplink Scheduler API
|
|
int sched::ul_sched(uint32_t tti, uint32_t cc_idx, srsenb::sched_interface::ul_sched_res_t& sched_result)
|
|
{
|
|
if (!configured) {
|
|
return 0;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(sched_mutex);
|
|
// Compute scheduling Result for tti_rx
|
|
uint32_t tti_rx = sched_utils::tti_subtract(tti, FDD_HARQ_DELAY_UL_MS + FDD_HARQ_DELAY_DL_MS);
|
|
|
|
if (cc_idx < carrier_schedulers.size()) {
|
|
const cc_sched_result& tti_sched = carrier_schedulers[cc_idx]->generate_tti_result(tti_rx);
|
|
|
|
// copy result
|
|
sched_result = tti_sched.ul_sched_result;
|
|
}
|
|
|
|
return SRSLTE_SUCCESS;
|
|
}
|
|
|
|
// Common way to access ue_db elements in a read locking way
|
|
template <typename Func>
|
|
int sched::ue_db_access(uint16_t rnti, Func f, const char* func_name)
|
|
{
|
|
std::lock_guard<std::mutex> lock(sched_mutex);
|
|
auto it = ue_db.find(rnti);
|
|
if (it != ue_db.end()) {
|
|
f(it->second);
|
|
} else {
|
|
if (func_name != nullptr) {
|
|
Error("User rnti=0x%x not found. Failed to call %s.\n", rnti, func_name);
|
|
} else {
|
|
Error("User rnti=0x%x not found.\n", rnti);
|
|
}
|
|
return SRSLTE_ERROR;
|
|
}
|
|
return SRSLTE_SUCCESS;
|
|
}
|
|
|
|
/*******************************************************
|
|
*
|
|
* Helper functions and common data types
|
|
*
|
|
*******************************************************/
|
|
|
|
void sched_cell_params_t::regs_deleter::operator()(srslte_regs_t* p)
|
|
{
|
|
if (p != nullptr) {
|
|
srslte_regs_free(p);
|
|
delete p;
|
|
}
|
|
}
|
|
|
|
rbg_interval rbg_interval::prbs_to_rbgs(const prb_interval& prbs, uint32_t P)
|
|
{
|
|
return rbg_interval{srslte::ceil_div(prbs.start(), P), srslte::ceil_div(prbs.stop(), P)};
|
|
}
|
|
|
|
prb_interval prb_interval::rbgs_to_prbs(const rbg_interval& rbgs, uint32_t P)
|
|
{
|
|
return prb_interval{rbgs.start() * P, rbgs.stop() * P};
|
|
}
|
|
|
|
rbg_interval rbg_interval::rbgmask_to_rbgs(const rbgmask_t& mask)
|
|
{
|
|
int rb_start = -1;
|
|
for (uint32_t i = 0; i < mask.size(); i++) {
|
|
if (rb_start == -1) {
|
|
if (mask.test(i)) {
|
|
rb_start = i;
|
|
}
|
|
} else {
|
|
if (!mask.test(i)) {
|
|
return rbg_interval(rb_start, i);
|
|
}
|
|
}
|
|
}
|
|
if (rb_start != -1) {
|
|
return rbg_interval(rb_start, mask.size());
|
|
} else {
|
|
return rbg_interval();
|
|
}
|
|
}
|
|
|
|
prb_interval prb_interval::riv_to_prbs(uint32_t riv, uint32_t nof_prbs, int nof_vrbs)
|
|
{
|
|
if (nof_vrbs < 0) {
|
|
nof_vrbs = nof_prbs;
|
|
}
|
|
uint32_t rb_start, l_crb;
|
|
srslte_ra_type2_from_riv(riv, &l_crb, &rb_start, nof_prbs, (uint32_t)nof_vrbs);
|
|
return {rb_start, rb_start + l_crb};
|
|
}
|
|
|
|
namespace sched_utils {
|
|
|
|
void generate_cce_location(srslte_regs_t* regs_,
|
|
sched_dci_cce_t* location,
|
|
uint32_t cfi,
|
|
uint32_t sf_idx,
|
|
uint16_t rnti)
|
|
{
|
|
*location = {};
|
|
|
|
srslte_dci_location_t loc[64];
|
|
uint32_t nloc = 0;
|
|
if (rnti == 0) {
|
|
nloc = srslte_pdcch_common_locations_ncce(srslte_regs_pdcch_ncce(regs_, cfi), loc, 64);
|
|
} else {
|
|
nloc = srslte_pdcch_ue_locations_ncce(srslte_regs_pdcch_ncce(regs_, cfi), loc, 64, sf_idx, rnti);
|
|
}
|
|
|
|
// Save to different format
|
|
for (uint32_t i = 0; i < nloc; i++) {
|
|
uint32_t l = loc[i].L;
|
|
location->cce_start[l][location->nof_loc[l]] = loc[i].ncce;
|
|
location->nof_loc[l]++;
|
|
}
|
|
}
|
|
|
|
} // namespace sched_utils
|
|
|
|
} // namespace srsenb
|