|
|
|
@ -22,8 +22,10 @@
|
|
|
|
|
#include "srsenb/hdr/stack/mac/nr/sched_nr.h"
|
|
|
|
|
#include "srsenb/hdr/stack/mac/common/mac_metrics.h"
|
|
|
|
|
#include "srsenb/hdr/stack/mac/nr/harq_softbuffer.h"
|
|
|
|
|
#include "srsenb/hdr/stack/mac/nr/sched_nr_cell.h"
|
|
|
|
|
#include "srsenb/hdr/stack/mac/nr/sched_nr_bwp.h"
|
|
|
|
|
#include "srsenb/hdr/stack/mac/nr/sched_nr_worker.h"
|
|
|
|
|
#include "srsran/common/phy_cfg_nr_default.h"
|
|
|
|
|
#include "srsran/common/string_helpers.h"
|
|
|
|
|
#include "srsran/common/thread_pool.h"
|
|
|
|
|
|
|
|
|
|
namespace srsenb {
|
|
|
|
@ -34,54 +36,272 @@ static int assert_ue_cfg_valid(uint16_t rnti, const sched_nr_interface::ue_cfg_t
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
class ul_sched_result_buffer
|
|
|
|
|
/// Class that stores events that are not specific to a CC (e.g. SRs, removal of UEs, buffer state updates)
|
|
|
|
|
class sched_nr::event_manager
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
explicit ul_sched_result_buffer(uint32_t nof_cc_)
|
|
|
|
|
/// class used to accummulate all processed event messages of a single {slot,cc} and print them in a single log line
|
|
|
|
|
struct logger {
|
|
|
|
|
explicit logger(int cc_, srslog::basic_logger& logger_) :
|
|
|
|
|
log_enabled(logger_.debug.enabled()), cc(cc_), sched_logger(logger_)
|
|
|
|
|
{}
|
|
|
|
|
logger(const logger&) = delete;
|
|
|
|
|
logger(logger&&) = delete;
|
|
|
|
|
logger& operator=(const logger&) = delete;
|
|
|
|
|
logger& operator=(logger&&) = delete;
|
|
|
|
|
~logger()
|
|
|
|
|
{
|
|
|
|
|
if (log_enabled and event_fmtbuf.size() > 0) {
|
|
|
|
|
if (cc < 0) {
|
|
|
|
|
sched_logger.debug("SCHED: slot events: [%s]", srsran::to_c_str(event_fmtbuf));
|
|
|
|
|
} else {
|
|
|
|
|
sched_logger.debug("SCHED: slot events, cc=%d: [%s]", cc, srsran::to_c_str(event_fmtbuf));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename... Args>
|
|
|
|
|
void push(const char* fmt, Args&&... args)
|
|
|
|
|
{
|
|
|
|
|
if (log_enabled) {
|
|
|
|
|
if (event_fmtbuf.size() > 0) {
|
|
|
|
|
fmt::format_to(event_fmtbuf, ", ");
|
|
|
|
|
}
|
|
|
|
|
fmt::format_to(event_fmtbuf, fmt, std::forward<Args>(args)...);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
bool log_enabled;
|
|
|
|
|
int cc;
|
|
|
|
|
srslog::basic_logger& sched_logger;
|
|
|
|
|
fmt::memory_buffer event_fmtbuf;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
explicit event_manager(sched_params_t& params) :
|
|
|
|
|
sched_logger(srslog::fetch_basic_logger(params.sched_cfg.logger_name)), carriers(params.cells.size())
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
/// Enqueue an event that does not map into a ue method (e.g. rem_user, add_user)
|
|
|
|
|
void enqueue_event(const char* event_name, srsran::move_callback<void(logger&)> ev)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::mutex> lock(event_mutex);
|
|
|
|
|
next_slot_events.emplace_back(event_name, std::move(ev));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Enqueue an event that directly maps into a ue method (e.g. ul_sr_info, ul_bsr, etc.)
|
|
|
|
|
/// Note: these events can be processed sequentially or in parallel, depending on whether the UE supports CA
|
|
|
|
|
void enqueue_ue_event(const char* event_name, uint16_t rnti, srsran::move_callback<void(ue&, logger&)> callback)
|
|
|
|
|
{
|
|
|
|
|
srsran_assert(rnti != SRSRAN_INVALID_RNTI, "Invalid rnti=0x%x passed to common event manager", rnti);
|
|
|
|
|
std::lock_guard<std::mutex> lock(event_mutex);
|
|
|
|
|
next_slot_ue_events.emplace_back(rnti, event_name, std::move(callback));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Enqueue feedback directed at a given UE in a given cell (e.g. ACKs, CQI)
|
|
|
|
|
void enqueue_ue_cc_feedback(const char* event_name,
|
|
|
|
|
uint16_t rnti,
|
|
|
|
|
uint32_t cc,
|
|
|
|
|
srsran::move_callback<void(ue_carrier&, logger&)> callback)
|
|
|
|
|
{
|
|
|
|
|
srsran_assert(rnti != SRSRAN_INVALID_RNTI, "Invalid rnti=0x%x passed to event manager", rnti);
|
|
|
|
|
srsran_assert(cc < carriers.size(), "Invalid cc=%d passed to event manager", cc);
|
|
|
|
|
std::lock_guard<std::mutex> lock(carriers[cc].event_cc_mutex);
|
|
|
|
|
carriers[cc].next_slot_ue_events.emplace_back(rnti, cc, event_name, std::move(callback));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Process all events that are not specific to a carrier or that are directed at CA-enabled UEs
|
|
|
|
|
/// Note: non-CA UEs are updated later in get_dl_sched, to leverage parallelism
|
|
|
|
|
void process_common(ue_map_t& ues)
|
|
|
|
|
{
|
|
|
|
|
for (auto& v : results) {
|
|
|
|
|
v.resize(nof_cc_);
|
|
|
|
|
// Extract pending feedback events
|
|
|
|
|
current_slot_ue_events.clear();
|
|
|
|
|
current_slot_events.clear();
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::mutex> ev_lock(event_mutex);
|
|
|
|
|
next_slot_ue_events.swap(current_slot_ue_events);
|
|
|
|
|
next_slot_events.swap(current_slot_events);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger evlogger(-1, sched_logger);
|
|
|
|
|
|
|
|
|
|
// non-UE specific events
|
|
|
|
|
for (event_t& ev : current_slot_events) {
|
|
|
|
|
ev.callback(evlogger);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (ue_event_t& ev : current_slot_ue_events) {
|
|
|
|
|
auto ue_it = ues.find(ev.rnti);
|
|
|
|
|
if (ue_it == ues.end()) {
|
|
|
|
|
sched_logger.warning("SCHED: \"%s\" called for inexistent rnti=0x%x.", ev.event_name, ev.rnti);
|
|
|
|
|
ev.rnti = SRSRAN_INVALID_RNTI;
|
|
|
|
|
} else if (ue_it->second->has_ca()) {
|
|
|
|
|
// events specific to existing UEs with CA
|
|
|
|
|
ev.callback(*ue_it->second, evlogger);
|
|
|
|
|
ev.rnti = SRSRAN_INVALID_RNTI;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ul_sched_t& add_ul_result(slot_point tti, uint32_t cc)
|
|
|
|
|
/// Process events synchronized during slot_indication() that are directed at non CA-enabled UEs
|
|
|
|
|
void process_cc_events(ue_map_t& ues, uint32_t cc)
|
|
|
|
|
{
|
|
|
|
|
if (not has_ul_result(tti, cc)) {
|
|
|
|
|
results[tti.to_uint()][cc].slot_ul = tti;
|
|
|
|
|
results[tti.to_uint()][cc].ul_res = {};
|
|
|
|
|
logger evlogger(cc, sched_logger);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
carriers[cc].current_slot_ue_events.clear();
|
|
|
|
|
std::lock_guard<std::mutex> lock(carriers[cc].event_cc_mutex);
|
|
|
|
|
carriers[cc].current_slot_ue_events.swap(carriers[cc].next_slot_ue_events);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (ue_event_t& ev : current_slot_ue_events) {
|
|
|
|
|
if (ev.rnti == SRSRAN_INVALID_RNTI) {
|
|
|
|
|
// events already processed
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
auto ue_it = ues.find(ev.rnti);
|
|
|
|
|
if (ue_it == ues.end()) {
|
|
|
|
|
sched_logger.warning("SCHED: \"%s\" called for inexistent rnti=0x%x.", ev.event_name, ev.rnti);
|
|
|
|
|
ev.rnti = SRSRAN_INVALID_RNTI;
|
|
|
|
|
} else if (not ue_it->second->has_ca() and ue_it->second->carriers[cc] != nullptr) {
|
|
|
|
|
ev.callback(*ue_it->second, evlogger);
|
|
|
|
|
ev.rnti = SRSRAN_INVALID_RNTI;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (ue_cc_event_t& ev : carriers[cc].current_slot_ue_events) {
|
|
|
|
|
auto ue_it = ues.find(ev.rnti);
|
|
|
|
|
if (ue_it != ues.end() and ue_it->second->carriers[cc] != nullptr) {
|
|
|
|
|
ev.callback(*ue_it->second->carriers[cc], evlogger);
|
|
|
|
|
} else {
|
|
|
|
|
sched_logger.warning("SCHED: \"%s\" called for inexistent rnti=0x%x,cc=%d.", ev.event_name, ev.rnti, ev.cc);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return results[tti.to_uint()][cc].ul_res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool has_ul_result(slot_point tti, uint32_t cc) const { return results[tti.to_uint()][cc].slot_ul == tti; }
|
|
|
|
|
private:
|
|
|
|
|
struct event_t {
|
|
|
|
|
const char* event_name;
|
|
|
|
|
srsran::move_callback<void(logger&)> callback;
|
|
|
|
|
event_t(const char* event_name_, srsran::move_callback<void(logger&)> c) :
|
|
|
|
|
event_name(event_name_), callback(std::move(c))
|
|
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
struct ue_event_t {
|
|
|
|
|
uint16_t rnti;
|
|
|
|
|
const char* event_name;
|
|
|
|
|
srsran::move_callback<void(ue&, logger&)> callback;
|
|
|
|
|
ue_event_t(uint16_t rnti_, const char* event_name_, srsran::move_callback<void(ue&, logger&)> c) :
|
|
|
|
|
rnti(rnti_), event_name(event_name_), callback(std::move(c))
|
|
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
struct ue_cc_event_t {
|
|
|
|
|
uint16_t rnti;
|
|
|
|
|
uint32_t cc;
|
|
|
|
|
const char* event_name;
|
|
|
|
|
srsran::move_callback<void(ue_carrier&, logger&)> callback;
|
|
|
|
|
ue_cc_event_t(uint16_t rnti_,
|
|
|
|
|
uint32_t cc_,
|
|
|
|
|
const char* event_name_,
|
|
|
|
|
srsran::move_callback<void(ue_carrier&, logger&)> c) :
|
|
|
|
|
rnti(rnti_), cc(cc_), event_name(event_name_), callback(std::move(c))
|
|
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
srslog::basic_logger& sched_logger;
|
|
|
|
|
|
|
|
|
|
std::mutex event_mutex;
|
|
|
|
|
srsran::deque<event_t> next_slot_events, current_slot_events;
|
|
|
|
|
srsran::deque<ue_event_t> next_slot_ue_events, current_slot_ue_events;
|
|
|
|
|
struct cc_events {
|
|
|
|
|
std::mutex event_cc_mutex;
|
|
|
|
|
srsran::deque<ue_cc_event_t> next_slot_ue_events, current_slot_ue_events;
|
|
|
|
|
};
|
|
|
|
|
std::vector<cc_events> carriers;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
class sched_nr::ue_metrics_manager
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
explicit ue_metrics_manager(ue_map_t& ues_) : ues(ues_) {}
|
|
|
|
|
|
|
|
|
|
void stop()
|
|
|
|
|
{
|
|
|
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
|
|
|
if (not stopped) {
|
|
|
|
|
stopped = true;
|
|
|
|
|
// requests during sched::stop may not be fulfilled by sched main thread
|
|
|
|
|
save_metrics_nolock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Blocking call that waits for the metrics to be filled
|
|
|
|
|
void get_metrics(mac_metrics_t& requested_metrics)
|
|
|
|
|
{
|
|
|
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
|
|
|
pending_metrics = &requested_metrics;
|
|
|
|
|
if (not stopped) {
|
|
|
|
|
cvar.wait(lock, [this]() { return pending_metrics == nullptr; });
|
|
|
|
|
} else {
|
|
|
|
|
save_metrics_nolock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ul_sched_t pop_ul_result(slot_point tti, uint32_t cc)
|
|
|
|
|
/// called from within the scheduler main thread to save metrics
|
|
|
|
|
void save_metrics()
|
|
|
|
|
{
|
|
|
|
|
if (has_ul_result(tti, cc)) {
|
|
|
|
|
results[tti.to_uint()][cc].slot_ul.clear();
|
|
|
|
|
return results[tti.to_uint()][cc].ul_res;
|
|
|
|
|
{
|
|
|
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
|
|
|
save_metrics_nolock();
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
cvar.notify_one();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
struct slot_result_t {
|
|
|
|
|
slot_point slot_ul;
|
|
|
|
|
ul_sched_t ul_res;
|
|
|
|
|
};
|
|
|
|
|
void save_metrics_nolock()
|
|
|
|
|
{
|
|
|
|
|
if (pending_metrics == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for (mac_ue_metrics_t& ue_metric : pending_metrics->ues) {
|
|
|
|
|
if (ues.contains(ue_metric.rnti) and ues[ue_metric.rnti]->carriers[0] != nullptr) {
|
|
|
|
|
auto& ue_cc = *ues[ue_metric.rnti]->carriers[0];
|
|
|
|
|
ue_metric.tx_brate = ue_cc.metrics.tx_brate;
|
|
|
|
|
ue_metric.tx_errors = ue_cc.metrics.tx_errors;
|
|
|
|
|
ue_metric.tx_pkts = ue_cc.metrics.tx_pkts;
|
|
|
|
|
ue_cc.metrics = {};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pending_metrics = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
srsran::circular_array<std::vector<slot_result_t>, TTIMOD_SZ> results;
|
|
|
|
|
ue_map_t& ues;
|
|
|
|
|
|
|
|
|
|
std::mutex mutex;
|
|
|
|
|
std::condition_variable cvar;
|
|
|
|
|
mac_metrics_t* pending_metrics = nullptr;
|
|
|
|
|
bool stopped = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
sched_nr::sched_nr() : logger(&srslog::fetch_basic_logger("MAC-NR")) {}
|
|
|
|
|
sched_nr::sched_nr() : logger(&srslog::fetch_basic_logger("MAC-NR")), metrics_handler(new ue_metrics_manager{ue_db}) {}
|
|
|
|
|
|
|
|
|
|
sched_nr::~sched_nr() {}
|
|
|
|
|
sched_nr::~sched_nr()
|
|
|
|
|
{
|
|
|
|
|
stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sched_nr::stop()
|
|
|
|
|
{
|
|
|
|
|
metrics_handler->stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int sched_nr::config(const sched_args_t& sched_cfg, srsran::const_span<cell_cfg_t> cell_list)
|
|
|
|
|
{
|
|
|
|
|
cfg = sched_params{sched_cfg};
|
|
|
|
|
cfg = sched_params_t{sched_cfg};
|
|
|
|
|
logger = &srslog::fetch_basic_logger(sched_cfg.logger_name);
|
|
|
|
|
|
|
|
|
|
// Initiate Common Sched Configuration
|
|
|
|
@ -90,150 +310,184 @@ int sched_nr::config(const sched_args_t& sched_cfg, srsran::const_span<cell_cfg_
|
|
|
|
|
cfg.cells.emplace_back(cc, cell_list[cc], cfg.sched_cfg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pending_events.reset(new event_manager{cfg});
|
|
|
|
|
|
|
|
|
|
// Initiate cell-specific schedulers
|
|
|
|
|
cells.reserve(cell_list.size());
|
|
|
|
|
for (uint32_t cc = 0; cc < cell_list.size(); ++cc) {
|
|
|
|
|
cells.emplace_back(new serv_cell_manager{cfg.cells[cc]});
|
|
|
|
|
cc_workers.resize(cfg.cells.size());
|
|
|
|
|
for (uint32_t cc = 0; cc < cfg.cells.size(); ++cc) {
|
|
|
|
|
cc_workers[cc].reset(new slot_cc_worker{cfg.cells[cc]});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pending_results.reset(new ul_sched_result_buffer(cell_list.size()));
|
|
|
|
|
sched_workers.reset(new sched_nr_impl::sched_worker_manager(ue_db, cfg, cells));
|
|
|
|
|
|
|
|
|
|
return SRSRAN_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sched_nr::ue_cfg(uint16_t rnti, const ue_cfg_t& uecfg)
|
|
|
|
|
{
|
|
|
|
|
srsran_assert(assert_ue_cfg_valid(rnti, uecfg) == SRSRAN_SUCCESS, "Invalid UE configuration");
|
|
|
|
|
sched_workers->enqueue_event(rnti, [this, rnti, uecfg]() { ue_cfg_impl(rnti, uecfg); });
|
|
|
|
|
pending_events->enqueue_event("ue_cfg", [this, rnti, uecfg](event_manager::logger& ev_logger) {
|
|
|
|
|
if (ue_cfg_impl(rnti, uecfg) == SRSRAN_SUCCESS) {
|
|
|
|
|
ev_logger.push("ue_cfg(0x{:x})", rnti);
|
|
|
|
|
} else {
|
|
|
|
|
logger->warning("Failed to create UE object for rnti=0x{:x}", rnti);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sched_nr::ue_rem(uint16_t rnti)
|
|
|
|
|
{
|
|
|
|
|
sched_workers->enqueue_event(rnti, [this, rnti]() {
|
|
|
|
|
auto ue_it = ue_db.find(rnti);
|
|
|
|
|
if (ue_it == ue_db.end()) {
|
|
|
|
|
logger->warning("SCHED: ue_rem(rnti) called for inexistent rnti=0x%x", rnti);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
pending_events->enqueue_event("ue_rem", [this, rnti](event_manager::logger& ev_logger) {
|
|
|
|
|
ue_db.erase(rnti);
|
|
|
|
|
logger->info("SCHED: Removed user rnti=0x%x", rnti);
|
|
|
|
|
ev_logger.push("ue_rem(0x{:x})", rnti);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool sched_nr::ue_exists(uint16_t rnti)
|
|
|
|
|
int sched_nr::add_ue_impl(uint16_t rnti, std::unique_ptr<sched_nr_impl::ue> u)
|
|
|
|
|
{
|
|
|
|
|
return ue_db.contains(rnti);
|
|
|
|
|
logger->info("SCHED: New user rnti=0x%x, cc=%d", rnti, cfg.cells[0].cc);
|
|
|
|
|
return ue_db.insert(rnti, std::move(u)).has_value() ? SRSRAN_SUCCESS : SRSRAN_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sched_nr::ue_cfg_impl(uint16_t rnti, const ue_cfg_t& uecfg)
|
|
|
|
|
int sched_nr::ue_cfg_impl(uint16_t rnti, const ue_cfg_t& uecfg)
|
|
|
|
|
{
|
|
|
|
|
if (not ue_db.contains(rnti)) {
|
|
|
|
|
auto ret = ue_db.insert(rnti, std::unique_ptr<ue>(new ue{rnti, uecfg, cfg}));
|
|
|
|
|
if (ret.has_value()) {
|
|
|
|
|
logger->info("SCHED: New user rnti=0x%x, cc=%d", rnti, cfg.cells[0].cc);
|
|
|
|
|
} else {
|
|
|
|
|
logger->error("SCHED: Failed to create new user rnti=0x%x", rnti);
|
|
|
|
|
return add_ue_impl(rnti, std::unique_ptr<ue>(new ue{rnti, uecfg, cfg}));
|
|
|
|
|
}
|
|
|
|
|
ue_db[rnti]->set_cfg(uecfg);
|
|
|
|
|
return SRSRAN_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NOTE: there is no parallelism in these operations
|
|
|
|
|
void sched_nr::slot_indication(slot_point slot_tx)
|
|
|
|
|
{
|
|
|
|
|
srsran_assert(worker_count.load(std::memory_order_relaxed) == 0,
|
|
|
|
|
"Call of sched slot_indication when previous TTI has not been completed");
|
|
|
|
|
// mark the start of slot.
|
|
|
|
|
current_slot_tx = slot_tx;
|
|
|
|
|
worker_count.store(static_cast<int>(cfg.cells.size()), std::memory_order_relaxed);
|
|
|
|
|
|
|
|
|
|
// process non-cc specific feedback if pending (e.g. SRs, buffer state updates, UE config) for CA-enabled UEs
|
|
|
|
|
// Note: non-CA UEs are updated later in get_dl_sched, to leverage parallelism
|
|
|
|
|
pending_events->process_common(ue_db);
|
|
|
|
|
|
|
|
|
|
// prepare CA-enabled UEs internal state for new slot
|
|
|
|
|
// Note: non-CA UEs are updated later in get_dl_sched, to leverage parallelism
|
|
|
|
|
for (auto& u : ue_db) {
|
|
|
|
|
if (u.second->has_ca()) {
|
|
|
|
|
u.second->new_slot(slot_tx);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
ue_db[rnti]->set_cfg(uecfg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If UE metrics were externally requested, store the current UE state
|
|
|
|
|
metrics_handler->save_metrics();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generate {pdcch_slot,cc} scheduling decision
|
|
|
|
|
int sched_nr::run_slot(slot_point slot_dl, uint32_t cc, dl_res_t& result)
|
|
|
|
|
sched_nr::dl_res_t* sched_nr::get_dl_sched(slot_point pdsch_tti, uint32_t cc)
|
|
|
|
|
{
|
|
|
|
|
// Copy UL results to intermediate buffer
|
|
|
|
|
ul_res_t& ul_res = pending_results->add_ul_result(slot_dl, cc);
|
|
|
|
|
srsran_assert(pdsch_tti == current_slot_tx, "Unexpected pdsch_tti slot received");
|
|
|
|
|
|
|
|
|
|
// Generate {slot_idx,cc} result
|
|
|
|
|
sched_workers->run_slot(slot_dl, cc, result, ul_res);
|
|
|
|
|
// process non-cc specific feedback if pending (e.g. SRs, buffer state updates, UE config) for non-CA UEs
|
|
|
|
|
pending_events->process_cc_events(ue_db, cc);
|
|
|
|
|
|
|
|
|
|
return SRSRAN_SUCCESS;
|
|
|
|
|
// prepare non-CA UEs internal state for new slot
|
|
|
|
|
for (auto& u : ue_db) {
|
|
|
|
|
if (not u.second->has_ca() and u.second->carriers[cc] != nullptr) {
|
|
|
|
|
u.second->new_slot(current_slot_tx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process pending CC-specific feedback, generate {slot_idx,cc} scheduling decision
|
|
|
|
|
sched_nr::dl_res_t* ret = cc_workers[cc]->run_slot(pdsch_tti, ue_db);
|
|
|
|
|
|
|
|
|
|
// decrement the number of active workers
|
|
|
|
|
int rem_workers = worker_count.fetch_sub(1, std::memory_order_release) - 1;
|
|
|
|
|
srsran_assert(rem_workers >= 0, "invalid number of calls to get_dl_sched(slot, cc)");
|
|
|
|
|
if (rem_workers == 0) {
|
|
|
|
|
// Last Worker to finish slot
|
|
|
|
|
// TODO: Sync sched results with ue_db state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Fetch {ul_slot,cc} UL scheduling decision
|
|
|
|
|
int sched_nr::get_ul_sched(slot_point slot_ul, uint32_t cc, ul_res_t& result)
|
|
|
|
|
sched_nr::ul_res_t* sched_nr::get_ul_sched(slot_point slot_ul, uint32_t cc)
|
|
|
|
|
{
|
|
|
|
|
if (not pending_results->has_ul_result(slot_ul, cc)) {
|
|
|
|
|
// sched result hasn't been generated
|
|
|
|
|
result.pucch.clear();
|
|
|
|
|
result.pusch.clear();
|
|
|
|
|
return SRSRAN_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = pending_results->pop_ul_result(slot_ul, cc);
|
|
|
|
|
return SRSRAN_SUCCESS;
|
|
|
|
|
return cc_workers[cc]->get_ul_sched(slot_ul);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sched_nr::get_metrics(mac_metrics_t& metrics)
|
|
|
|
|
{
|
|
|
|
|
sched_workers->get_metrics(metrics);
|
|
|
|
|
metrics_handler->get_metrics(metrics);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int sched_nr::dl_rach_info(uint32_t cc, const rar_info_t& rar_info)
|
|
|
|
|
int sched_nr::dl_rach_info(const rar_info_t& rar_info, const ue_cfg_t& uecfg)
|
|
|
|
|
{
|
|
|
|
|
sched_workers->enqueue_cc_event(cc, [this, cc, rar_info]() { cells[cc]->bwps[0].ra.dl_rach_info(rar_info); });
|
|
|
|
|
// enqueue UE creation event + RACH handling
|
|
|
|
|
auto add_ue = [this, uecfg, rar_info](event_manager::logger& ev_logger) {
|
|
|
|
|
// create user
|
|
|
|
|
// Note: UEs being created in sched main thread, which has higher priority
|
|
|
|
|
logger->info("SCHED: New user rnti=0x%x, cc=%d", rar_info.temp_crnti, uecfg.carriers[0].cc);
|
|
|
|
|
std::unique_ptr<ue> u{new ue{rar_info.temp_crnti, uecfg, cfg}};
|
|
|
|
|
|
|
|
|
|
uint16_t rnti = rar_info.temp_crnti;
|
|
|
|
|
if (add_ue_impl(rnti, std::move(u)) == SRSRAN_SUCCESS) {
|
|
|
|
|
ev_logger.push("dl_rach_info(temp c-rnti=0x{:x})", rar_info.temp_crnti);
|
|
|
|
|
// RACH is handled only once the UE object is created and inserted in the ue_db
|
|
|
|
|
uint32_t cc = uecfg.carriers[0].cc;
|
|
|
|
|
cc_workers[cc]->dl_rach_info(rar_info);
|
|
|
|
|
} else {
|
|
|
|
|
logger->warning("Failed to create UE object with rnti=0x%x", rar_info.temp_crnti);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
pending_events->enqueue_event("dl_rach_info", add_ue);
|
|
|
|
|
return SRSRAN_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sched_nr::dl_ack_info(uint16_t rnti, uint32_t cc, uint32_t pid, uint32_t tb_idx, bool ack)
|
|
|
|
|
{
|
|
|
|
|
sched_workers->enqueue_cc_feedback(rnti, cc, [this, pid, tb_idx, ack](ue_carrier& ue_cc) {
|
|
|
|
|
int tbs = ue_cc.harq_ent.dl_ack_info(pid, tb_idx, ack);
|
|
|
|
|
if (tbs >= 0) {
|
|
|
|
|
std::lock_guard<std::mutex> lock(ue_cc.metrics_mutex);
|
|
|
|
|
if (ack) {
|
|
|
|
|
ue_cc.metrics.tx_brate += tbs;
|
|
|
|
|
} else {
|
|
|
|
|
ue_cc.metrics.tx_errors++;
|
|
|
|
|
}
|
|
|
|
|
ue_cc.metrics.tx_pkts++;
|
|
|
|
|
} else {
|
|
|
|
|
logger->warning("SCHED: rnti=0x%x, received DL HARQ-ACK for empty pid=%d", ue_cc.rnti, pid);
|
|
|
|
|
auto callback = [pid, tb_idx, ack](ue_carrier& ue_cc, event_manager::logger& ev_logger) {
|
|
|
|
|
if (ue_cc.dl_ack_info(pid, tb_idx, ack) >= 0) {
|
|
|
|
|
ev_logger.push("0x{:x}: dl_ack_info(pid={}, ack={})", ue_cc.rnti, pid, ack ? "OK" : "KO");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
pending_events->enqueue_ue_cc_feedback("dl_ack_info", rnti, cc, callback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sched_nr::ul_crc_info(uint16_t rnti, uint32_t cc, uint32_t pid, bool crc)
|
|
|
|
|
{
|
|
|
|
|
sched_workers->enqueue_cc_feedback(rnti, cc, [this, pid, crc](ue_carrier& ue_cc) {
|
|
|
|
|
if (ue_cc.harq_ent.ul_crc_info(pid, crc) < 0) {
|
|
|
|
|
logger->warning("SCHED: rnti=0x%x, received CRC for empty pid=%d", ue_cc.rnti, pid);
|
|
|
|
|
auto callback = [pid, crc](ue_carrier& ue_cc, event_manager::logger& ev_logger) {
|
|
|
|
|
if (ue_cc.ul_crc_info(pid, crc) >= 0) {
|
|
|
|
|
ev_logger.push("0x{:x}: ul_crc_info(pid={}, crc={})", ue_cc.rnti, pid, crc ? "OK" : "KO");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
pending_events->enqueue_ue_cc_feedback("ul_crc_info", rnti, cc, callback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sched_nr::ul_sr_info(uint16_t rnti)
|
|
|
|
|
{
|
|
|
|
|
sched_workers->enqueue_event(rnti, [this, rnti]() {
|
|
|
|
|
if (ue_db.contains(rnti)) {
|
|
|
|
|
ue_db[rnti]->ul_sr_info();
|
|
|
|
|
} else {
|
|
|
|
|
logger->warning("Received SR for inexistent rnti=0x%x", rnti);
|
|
|
|
|
}
|
|
|
|
|
pending_events->enqueue_ue_event("ul_sr_info", rnti, [](ue& u, event_manager::logger& evlogger) {
|
|
|
|
|
u.ul_sr_info();
|
|
|
|
|
evlogger.push("0x{:x}: ul_sr_info()", u.rnti);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sched_nr::ul_bsr(uint16_t rnti, uint32_t lcg_id, uint32_t bsr)
|
|
|
|
|
{
|
|
|
|
|
sched_workers->enqueue_event(rnti, [this, rnti, lcg_id, bsr]() {
|
|
|
|
|
if (ue_db.contains(rnti)) {
|
|
|
|
|
ue_db[rnti]->ul_bsr(lcg_id, bsr);
|
|
|
|
|
} else {
|
|
|
|
|
logger->warning("Received BSR=%d for inexistent rnti=0x%x", bsr, rnti);
|
|
|
|
|
}
|
|
|
|
|
pending_events->enqueue_ue_event("ul_bsr", rnti, [lcg_id, bsr](ue& u, event_manager::logger& evlogger) {
|
|
|
|
|
u.ul_bsr(lcg_id, bsr);
|
|
|
|
|
evlogger.push("0x{:x}: ul_bsr(lcg={}, bsr={})", u.rnti, lcg_id, bsr);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sched_nr::dl_buffer_state(uint16_t rnti, uint32_t lcid, uint32_t newtx, uint32_t retx)
|
|
|
|
|
{
|
|
|
|
|
sched_workers->enqueue_event(rnti, [this, rnti, lcid, newtx, retx]() {
|
|
|
|
|
if (ue_db.contains(rnti)) {
|
|
|
|
|
ue_db[rnti]->rlc_buffer_state(lcid, newtx, retx);
|
|
|
|
|
} else {
|
|
|
|
|
logger->warning("Received DL buffer state=%d/%d for inexistent rnti=0x%x", newtx, retx, rnti);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
pending_events->enqueue_ue_event(
|
|
|
|
|
"dl_buffer_state", rnti, [lcid, newtx, retx](ue& u, event_manager::logger& event_logger) {
|
|
|
|
|
u.rlc_buffer_state(lcid, newtx, retx);
|
|
|
|
|
event_logger.push("0x{:x}: dl_buffer_state(lcid={}, bsr={},{})", u.rnti, lcid, newtx, retx);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define VERIFY_INPUT(cond, msg, ...) \
|
|
|
|
|