/** * * \section COPYRIGHT * * Copyright 2013-2017 Software Radio Systems Limited * * \section LICENSE * * This file is part of srsLTE. * * srsUE 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. * * srsUE 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 #include #include #include #include #include "srslte/srslte.h" #include "srslte/common/pdu.h" #include "mac/scheduler_ue.h" #include "mac/scheduler.h" #define Error(fmt, ...) log_h->error_line(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define Warning(fmt, ...) log_h->warning_line(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define Info(fmt, ...) log_h->info_line(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define Debug(fmt, ...) log_h->debug_line(__FILE__, __LINE__, fmt, ##__VA_ARGS__) /****************************************************** * UE class * ******************************************************/ namespace srsenb { /******************************************************* * * Initialization and configuration functions * *******************************************************/ sched_ue::sched_ue() { reset(); } void sched_ue::set_cfg(uint16_t rnti_, sched_interface::ue_cfg_t *cfg_, sched_interface::cell_cfg_t *cell_cfg, srslte_regs_t *regs, srslte::log *log_h_) { reset(); rnti = rnti_; log_h = log_h_; memcpy(&cell, &cell_cfg->cell, sizeof(srslte_cell_t)); max_mcs_dl = 28; max_mcs_ul = 28; if (cfg_) { memcpy(&cfg, cfg_, sizeof(sched_interface::ue_cfg_t)); } Info("SCHED: Added user rnti=0x%x\n", rnti); for (int i=0;i 4) { len -= 4; } if (lcid < sched_interface::MAX_LC) { if (bearer_is_ul(&lch[lcid])) { if (lch[lcid].bsr > (int) len) { lch[lcid].bsr -= len; } else { lch[lcid].bsr = 0; } } } Debug("SCHED: recv_len=%d, lcid=%d, bsr={%d,%d,%d,%d}\n", len, lcid, lch[0].bsr, lch[1].bsr, lch[2].bsr, lch[3].bsr); } void sched_ue::set_ul_crc(uint32_t tti, bool crc_res) { get_ul_harq(tti)->set_ack(crc_res); } void sched_ue::set_dl_cqi(uint32_t tti, uint32_t cqi) { dl_cqi = cqi; dl_cqi_tti = tti; } void sched_ue::set_ul_cqi(uint32_t tti, uint32_t cqi, uint32_t ul_ch_code) { ul_cqi = cqi; ul_cqi_tti = tti; } void sched_ue::tpc_inc() { if (power_headroom > 0) { next_tpc_pusch = 3; next_tpc_pucch = 3; } log_h->info("SCHED: Set TCP=%d for rnti=0x%x\n", next_tpc_pucch, rnti); } void sched_ue::tpc_dec() { next_tpc_pusch = 0; next_tpc_pucch = 0; log_h->info("SCHED: Set TCP=%d for rnti=0x%x\n", next_tpc_pucch, rnti); } /******************************************************* * * Functions used to generate DCI grants * *******************************************************/ // Generates a Format1 grant int sched_ue::generate_format1(dl_harq_proc *h, sched_interface::dl_sched_data_t *data, uint32_t tti, uint32_t cfi) { srslte_ra_dl_dci_t *dci = &data->dci; bzero(dci, sizeof(srslte_ra_dl_dci_t)); uint32_t sf_idx = tti%10; int mcs = 0; int tbs = 0; dci->alloc_type = SRSLTE_RA_ALLOC_TYPE0; dci->type0_alloc.rbg_bitmask = h->get_rbgmask(); // If this is the first transmission for this UE, make room for MAC Contention Resolution ID bool need_conres_ce = false; if (is_first_dl_tx()) { need_conres_ce = true; } if (h->is_empty()) { uint32_t req_bytes = get_pending_dl_new_data(tti); uint32_t nof_prb = format1_count_prb(h->get_rbgmask(), cell.nof_prb); srslte_ra_dl_grant_t grant; srslte_ra_dl_dci_to_grant_prb_allocation(dci, &grant, cell.nof_prb); uint32_t nof_ctrl_symbols = cfi+(cell.nof_prb<10?1:0); uint32_t nof_re = srslte_ra_dl_grant_nof_re(&grant, cell, sf_idx, nof_ctrl_symbols); if (fixed_mcs_dl < 0) { tbs = alloc_tbs_dl(nof_prb, nof_re, req_bytes, &mcs); } else { tbs = srslte_ra_tbs_from_idx(srslte_ra_tbs_idx_from_mcs(fixed_mcs_dl), nof_prb)/8; mcs = fixed_mcs_dl; } h->new_tx(tti, mcs, tbs, data->dci_location.ncce); Debug("SCHED: Alloc format1 new mcs=%d, tbs=%d, nof_prb=%d, req_bytes=%d\n", mcs, tbs, nof_prb, req_bytes); } else { h->new_retx(tti, &mcs, &tbs); Debug("SCHED: Alloc format1 previous mcs=%d, tbs=%d\n", mcs, tbs); } // Allocate MAC ConRes CE if (need_conres_ce) { data->pdu[0].lcid = srslte::sch_subh::CON_RES_ID; data->nof_pdu_elems++; Info("SCHED: Added MAC Contention Resolution CE for rnti=0x%x\n", rnti); } int rem_tbs = tbs; int x = 0; do { x = alloc_pdu(rem_tbs, &data->pdu[data->nof_pdu_elems]); rem_tbs -= x; if (x) { data->nof_pdu_elems++; } } while(rem_tbs > 0 && x > 0); data->rnti = rnti; if (tbs > 0) { dci->harq_process = h->get_id(); dci->mcs_idx = mcs; dci->rv_idx = sched::get_rvidx(h->nof_retx()); dci->ndi = h->get_ndi(); dci->tpc_pucch = next_tpc_pucch; next_tpc_pucch = 1; data->tbs = tbs; dci->tb_en[0] = true; dci->tb_en[1] = false; } return tbs; } int sched_ue::generate_format0(ul_harq_proc *h, sched_interface::ul_sched_data_t *data, uint32_t tti, bool cqi_request) { srslte_ra_ul_dci_t *dci = &data->dci; bzero(dci, sizeof(srslte_ra_ul_dci_t)); int mcs = 0; int tbs = 0; ul_harq_proc::ul_alloc_t allocation = h->get_alloc(); if (h->get_rar_mcs(&mcs)) { tbs = srslte_ra_tbs_from_idx(srslte_ra_tbs_idx_from_mcs(mcs), allocation.L)/8; h->new_tx(tti, mcs, tbs); } else if (h->is_empty()) { uint32_t req_bytes = get_pending_ul_new_data(tti); uint32_t N_srs = 0; uint32_t nof_re = (2*(SRSLTE_CP_NSYMB(cell.cp)-1) - N_srs)*allocation.L*SRSLTE_NRE; if (fixed_mcs_ul < 0) { tbs = alloc_tbs_ul(allocation.L, nof_re, req_bytes, &mcs); } else { tbs = srslte_ra_tbs_from_idx(srslte_ra_tbs_idx_from_mcs(fixed_mcs_ul), allocation.L)/8; mcs = fixed_mcs_ul; } h->new_tx(tti, mcs, tbs); } else { h->new_retx(tti, &mcs, NULL); tbs = srslte_ra_tbs_from_idx(srslte_ra_tbs_idx_from_mcs(mcs), allocation.L)/8; } data->rnti = rnti; data->tbs = tbs; if (tbs > 0) { dci->type2_alloc.L_crb = allocation.L; dci->type2_alloc.RB_start = allocation.RB_start; dci->mcs_idx = mcs; dci->rv_idx = sched::get_rvidx(h->nof_retx()); dci->ndi = h->get_ndi(); dci->cqi_request = cqi_request; dci->freq_hop_fl = srslte_ra_ul_dci_t::SRSLTE_RA_PUSCH_HOP_DISABLED; dci->tpc_pusch = next_tpc_pusch; next_tpc_pusch = 1; } return tbs; } /******************************************************* * * Functions used by scheduler or scheduler metric objects * *******************************************************/ bool sched_ue::bearer_is_ul(ue_bearer_t *lch) { return lch->cfg.direction == sched_interface::ue_bearer_cfg_t::UL || lch->cfg.direction == sched_interface::ue_bearer_cfg_t::BOTH; } bool sched_ue::bearer_is_dl(ue_bearer_t *lch) { return lch->cfg.direction == sched_interface::ue_bearer_cfg_t::DL || lch->cfg.direction == sched_interface::ue_bearer_cfg_t::BOTH; } uint32_t sched_ue::get_max_retx() { return cfg.maxharq_tx; } bool sched_ue::is_first_dl_tx() { for (int i=0;i 0) { return false; } } return true; } bool sched_ue::needs_cqi(uint32_t tti, bool will_be_sent) { bool ret = false; if (phy_config_dedicated_enabled && cfg.aperiodic_cqi_period && get_pending_dl_new_data(tti) > 0) { uint32_t interval = srslte_tti_interval(tti, dl_cqi_tti); bool needscqi = interval >= cfg.aperiodic_cqi_period; if (needscqi) { uint32_t interval_sent = srslte_tti_interval(tti, cqi_request_tti); if (interval_sent >= 16) { if (will_be_sent) { cqi_request_tti = tti; } Debug("SCHED: Needs_cqi, last_sent=%d, will_be_sent=%d\n", cqi_request_tti, will_be_sent); ret = true; } } } return ret; } uint32_t sched_ue::get_pending_dl_new_data(uint32_t tti) { uint32_t pending_data = 0; for (int i=0;i pending_ul_data) { pending_data -= pending_ul_data; } else { pending_data = 0; } if (pending_data) { Debug("SCHED: pending_data=%d, pending_ul_data=%d, bsr={%d,%d,%d,%d}\n", pending_data,pending_ul_data, lch[0].bsr, lch[1].bsr, lch[2].bsr, lch[3].bsr); } return pending_data; } uint32_t sched_ue::get_pending_ul_old_data() { uint32_t pending_data = 0; for (int i=0;i 0) { nbytes = tbs; } else if (tbs < 0) { return 0; } } return n; } uint32_t sched_ue::get_required_prb_ul(uint32_t req_bytes) { int mcs = 0; int tbs = 0; uint32_t nbytes = 0; uint32_t N_srs = 0; uint32_t n = 0; if (req_bytes == 0) { return 0; } for (n=1;n 0) { nbytes = tbs; } } while (!srslte_dft_precoding_valid_prb(n) && n<=cell.nof_prb) { n++; } return n; } bool sched_ue::is_sr_triggered() { return sr; } /* Gets HARQ process with oldest pending retx */ dl_harq_proc* sched_ue::get_pending_dl_harq(uint32_t tti) { int oldest_idx=-1; uint32_t oldest_tti = 0; for (int i=0;i oldest_tti) { oldest_idx = i; oldest_tti = x; } } } if (oldest_idx >= 0) { return &dl_harq[oldest_idx]; } else { return NULL; } } dl_harq_proc* sched_ue::get_empty_dl_harq() { for (int i=0;i max_coderate); Debug("SCHED: CQI=%d, l=%d, nof_bits=%d, coderate=%.2f, max_coderate=%.2f\n", dl_cqi, l, nof_bits, coderate, max_coderate); return l; } sched_ue::sched_dci_cce_t* sched_ue::get_locations(uint32_t cfi, uint32_t sf_idx) { if (cfi > 0 && cfi <= 3) { return &dci_locations[cfi-1][sf_idx]; } else { Error("SCHED: Invalid CFI=%d\n", cfi); return &dci_locations[0][sf_idx]; } } /* Allocates first available RLC PDU */ int sched_ue::alloc_pdu(int tbs_bytes, sched_interface::dl_sched_pdu_t* pdu) { // TODO: Implement lcid priority (now lowest index is lowest priority) int x = 0; int i = 0; for (i=0;ilcid = i-1; pdu->nbytes = x; Debug("SCHED: Allocated lcid=%d, nbytes=%d, tbs_bytes=%d\n", pdu->lcid, pdu->nbytes, tbs_bytes); } return x; } uint32_t sched_ue::format1_count_prb(uint32_t bitmask, uint32_t cell_nof_prb) { uint32_t P = srslte_ra_type0_P(cell_nof_prb); uint32_t nb = (int) ceilf((float) cell_nof_prb / P); uint32_t nof_prb = 0; for (uint32_t i = 0; i < nb; i++) { if (bitmask & (1 << (nb - i - 1))) { for (uint32_t j = 0; j < P; j++) { if (i*P+j < cell_nof_prb) { nof_prb++; } } } } return nof_prb; } int sched_ue::cqi_to_tbs(uint32_t cqi, uint32_t nof_prb, uint32_t nof_re, uint32_t max_mcs, uint32_t max_Qm, uint32_t *mcs) { float max_coderate = srslte_cqi_to_coderate(cqi); int sel_mcs = max_mcs+1; float coderate = 99; float eff_coderate = 99; uint32_t Qm = 1; int tbs = 0; do { sel_mcs--; uint32_t tbs_idx = srslte_ra_tbs_idx_from_mcs(sel_mcs); tbs = srslte_ra_tbs_from_idx(tbs_idx, nof_prb); coderate = srslte_coderate(tbs, nof_re); Qm = SRSLTE_MIN(max_Qm, srslte_mod_bits_x_symbol(srslte_ra_mod_from_mcs(sel_mcs))); eff_coderate = coderate/Qm; } while((sel_mcs > 0 && coderate > max_coderate) || eff_coderate > 0.930); if (mcs) { *mcs = (uint32_t) sel_mcs; } return tbs; } int sched_ue::alloc_tbs_dl(uint32_t nof_prb, uint32_t nof_re, uint32_t req_bytes, int *mcs) { return alloc_tbs(nof_prb, nof_re, req_bytes, false, mcs); } int sched_ue::alloc_tbs_ul(uint32_t nof_prb, uint32_t nof_re, uint32_t req_bytes, int *mcs) { return alloc_tbs(nof_prb, nof_re, req_bytes, true, mcs); } /* In this scheduler we tend to use all the available bandwidth and select the MCS * that approximates the minimum between the capacity and the requested rate */ int sched_ue::alloc_tbs(uint32_t nof_prb, uint32_t nof_re, uint32_t req_bytes, bool is_ul, int *mcs) { uint32_t sel_mcs = 0; uint32_t cqi = is_ul?ul_cqi:dl_cqi; uint32_t max_mcs = is_ul?max_mcs_ul:max_mcs_dl; uint32_t max_Qm = is_ul?4:6; // Allow 16-QAM in PUSCH Only // TODO: Compute real spectral efficiency based on PUSCH-UCI configuration if (has_pucch && is_ul) { cqi-=3; } int tbs = cqi_to_tbs(cqi, nof_prb, nof_re, max_mcs, max_Qm, &sel_mcs)/8; /* If less bytes are requested, lower the MCS */ if (tbs > (int) req_bytes && req_bytes > 0) { uint32_t req_tbs_idx = srslte_ra_tbs_to_table_idx(req_bytes*8, nof_prb); uint32_t req_mcs = srslte_ra_mcs_from_tbs_idx(req_tbs_idx); if (req_mcs < sel_mcs) { sel_mcs = req_mcs; tbs = srslte_ra_tbs_from_idx(req_tbs_idx, nof_prb)/8; } } // Avoid the unusual case n_prb=1, mcs=6 tbs=328 (used in voip) if (nof_prb == 1 && sel_mcs == 6) { sel_mcs--; } if (mcs && tbs >= 0) { *mcs = (int) sel_mcs; } return tbs; } }