diff --git a/openair2/LAYER2/MAC/slicing/slicing.c b/openair2/LAYER2/MAC/slicing/slicing.c index 5dc2318776cb3434d077784a7b8a9ae312e4da83..bc084d0bc7b5e97f9ed791bf0dda8aa31792c885 100644 --- a/openair2/LAYER2/MAC/slicing/slicing.c +++ b/openair2/LAYER2/MAC/slicing/slicing.c @@ -604,3 +604,403 @@ pp_impl_param_t static_ul_init(module_id_t mod_id, int CC_id) { return sttc; } + +/************************* NVS Slicing Implementation **************************/ + +typedef struct { + float exp; // exponential weight. mov. avg for weight calc + int rb; // number of RBs this slice has been scheduled in last round + float eff; // effective rate for rate slices + float beta_eff; // averaging coeff so we average over roughly one second + int active; // activity state for rate slices +} _nvs_int_t; + +int _nvs_admission_control(const slice_info_t *si, + const nvs_slice_param_t *p, + int idx) { + if (p->type != NVS_RATE && p->type != NVS_RES) + RET_FAIL(-1, "%s(): invalid slice type %d\n", __func__, p->type); + if (p->type == NVS_RATE && p->Mbps_reserved > p->Mbps_reference) + RET_FAIL(-1, + "%s(): a rate slice cannot reserve more than the reference rate\n", + __func__); + if (p->type == NVS_RES && p->pct_reserved > 1.0f) + RET_FAIL(-1, "%s(): cannot reserve more than 1.0\n", __func__); + float sum_req = 0.0f; + for (int i = 0; i < si->num; ++i) { + const nvs_slice_param_t *sp = i == idx ? p : si->s[i]->algo_data; + if (sp->type == NVS_RATE) + sum_req += sp->Mbps_reserved / sp->Mbps_reference; + else + sum_req += sp->pct_reserved; + } + if (idx < 0) { /* not an existing slice */ + if (p->type == NVS_RATE) + sum_req += p->Mbps_reserved / p->Mbps_reference; + else + sum_req += p->pct_reserved; + } + if (sum_req > 1.0) + RET_FAIL(-3, + "%s(): admission control failed: sum of resources is %f > 1.0\n", + __func__, sum_req); + return 0; +} + +int addmod_nvs_slice_dl(slice_info_t *si, + int id, + char *label, + void *algo, + void *slice_params_dl) { + nvs_slice_param_t *dl = slice_params_dl; + int index = _exists_slice(si->num, si->s, id); + if (index < 0 && si->num >= MAX_NVS_SLICES) + RET_FAIL(-2, "%s(): cannot handle more than %d slices\n", __func__, MAX_NVS_SLICES); + + if (index < 0 && !dl) + RET_FAIL(-100, "%s(): no parameters for new slice %d, aborting\n", __func__, id); + + if (dl) { + int rc = _nvs_admission_control(si, dl, index); + if (rc < 0) + return rc; + } + + slice_t *s = NULL; + if (index >= 0) { + s = si->s[index]; + if (label) { + if (s->label) free(s->label); + s->label = label; + } + if (algo) { + s->dl_algo.unset(&s->dl_algo.data); + s->dl_algo = *(default_sched_dl_algo_t *) algo; + if (!s->dl_algo.data) + s->dl_algo.data = s->dl_algo.setup(); + } + if (dl) { + free(s->algo_data); + s->algo_data = dl; + } else { /* we have no parameters: we are done */ + return index; + } + } else { + if (!algo) + RET_FAIL(-14, "%s(): no scheduler algorithm provided\n", __func__); + + s = _add_slice(&si->num, si->s); + if (!s) + RET_FAIL(-4, "%s(): cannot allocate memory for slice\n", __func__); + s->int_data = malloc(sizeof(_nvs_int_t)); + if (!s->int_data) + RET_FAIL(-5, "%s(): cannot allocate memory for slice internal data\n", __func__); + + s->id = id; + s->label = label; + s->dl_algo = *(default_sched_dl_algo_t *) algo; + if (!s->dl_algo.data) + s->dl_algo.data = s->dl_algo.setup(); + s->algo_data = dl; + } + + _nvs_int_t *nvs_p = s->int_data; + /* reset all slice-internal parameters */ + nvs_p->rb = 0; + nvs_p->active = 0; + if (dl->type == NVS_RATE) { + nvs_p->exp = dl->Mbps_reserved / dl->Mbps_reference; + nvs_p->eff = dl->Mbps_reference; + } else { + nvs_p->exp = dl->pct_reserved; + nvs_p->eff = 0; // not used + } + // scale beta so we (roughly) average the eff rate over 1s + nvs_p->beta_eff = BETA / nvs_p->exp; + + return index < 0 ? si->num - 1 : index; +} + +//int addmod_nvs_slice_ul(slice_info_t *si, +// int id, +// char *label, +// void *slice_params_ul) { +// nvs_slice_param_t *sp = slice_params_ul; +// int index = _exists_slice(si->num, si->s, id); +// if (index < 0 && si->num >= MAX_NVS_SLICES) +// RET_FAIL(-2, "%s(): cannot handle more than %d slices\n", __func__, MAX_NVS_SLICES); +// +// int rc = _nvs_admission_control(si->num, si->s, sp, index); +// if (rc < 0) +// return rc; +// +// slice_t *ns = NULL; +// if (index < 0) { +// ns = _add_slice(&si->num, si->s); +// if (!ns) +// RET_FAIL(-4, "%s(): cannot allocate memory for slice\n", __func__); +// ns->id = id; +// ns->int_data = malloc(sizeof(_nvs_int_t)); +// if (!ns->int_data) +// RET_FAIL(-5, "%s(): cannot allocate memory for slice internal data\n", +// __func__); +// } else { +// ns = si->s[index]; +// free(ns->algo_data); +// } +// if (label) { +// if (ns->label) +// free(ns->label); +// ns->label = label; +// } +// ns->algo_data = sp; +// _nvs_int_t *nvs_p = ns->int_data; +// nvs_p->rb = 0; +// nvs_p->active = 0; +// if (sp->type == NVS_RATE) { +// nvs_p->exp = sp->Mbps_reserved; +// nvs_p->eff = sp->Mbps_reference; +// } else { +// nvs_p->exp = sp->pct_reserved; +// nvs_p->eff = 0; // not used +// } +// +// return si->num - 1; +//} + +int remove_nvs_slice_dl(slice_info_t *si, uint8_t slice_idx) { + if (slice_idx == 0) + return 0; + slice_t *sr = _remove_slice(&si->num, si->s, si->UE_assoc_slice, slice_idx); + if (!sr) + return 0; + free(sr->algo_data); + free(sr->int_data); + sr->dl_algo.unset(&sr->dl_algo.data); + free(sr); + return 1; +} + +//int remove_nvs_slice_ul(slice_info_t *si, uint8_t slice_idx) { +// if (slice_idx == 0) +// return 0; +// slice_t *sr = _remove_slice(&si->num, si->s, si->UE_assoc_slice, slice_idx); +// if (!sr) +// return 0; +// free(sr->algo_data); +// free(sr->int_data); +// free(sr); +// return 1; +//} + +void nvs_dl(module_id_t mod_id, + int CC_id, + frame_t frame, + sub_frame_t subframe) { + UE_info_t *UE_info = &RC.mac[mod_id]->UE_info; + + store_dlsch_buffer(mod_id, CC_id, frame, subframe); + + slice_info_t *si = RC.mac[mod_id]->pre_processor_dl.slices; + const COMMON_channels_t *cc = &RC.mac[mod_id]->common_channels[CC_id]; + const uint8_t harq_pid = frame_subframe2_dl_harq_pid(cc->tdd_Config, frame, subframe); + for (int UE_id = UE_info->list.head; UE_id >= 0; UE_id = UE_info->list.next[UE_id]) { + UE_sched_ctrl_t *ue_sched_ctrl = &UE_info->UE_sched_ctrl[UE_id]; + + /* initialize per-UE scheduling information */ + ue_sched_ctrl->pre_nb_available_rbs[CC_id] = 0; + ue_sched_ctrl->dl_pow_off[CC_id] = 2; + memset(ue_sched_ctrl->rballoc_sub_UE[CC_id], 0, sizeof(ue_sched_ctrl->rballoc_sub_UE[CC_id])); + ue_sched_ctrl->pre_dci_dl_pdu_idx = -1; + + const UE_TEMPLATE *UE_template = &UE_info->UE_template[CC_id][UE_id]; + const uint8_t round = UE_info->UE_sched_ctrl[UE_id].round[CC_id][harq_pid]; + /* if UE has data or retransmission, mark respective slice as active */ + if (UE_template->dl_buffer_total > 0 || round != 8) { + const int idx = si->UE_assoc_slice[UE_id]; + if (idx >= 0) + ((_nvs_int_t *)si->s[idx]->int_data)->active = 1; + } + } + + const int N_RBG = to_rbg(RC.mac[mod_id]->common_channels[CC_id].mib->message.dl_Bandwidth); + const int RBGsize = get_min_rb_unit(mod_id, CC_id); + uint8_t *vrb_map = RC.mac[mod_id]->common_channels[CC_id].vrb_map; + uint8_t rbgalloc_mask[N_RBG_MAX]; + int n_rbg_sched = 0; + for (int i = 0; i < N_RBG; i++) { + // calculate mask: init to one + "AND" with vrb_map: + // if any RB in vrb_map is blocked (1), the current RBG will be 0 + rbgalloc_mask[i] = 1; + for (int j = 0; j < RBGsize; j++) + rbgalloc_mask[i] &= !vrb_map[RBGsize * i + j]; + n_rbg_sched += rbgalloc_mask[i]; + } + + const int N_RB_DL = to_prb(RC.mac[mod_id]->common_channels[CC_id].mib->message.dl_Bandwidth); + float maxw = 0.0f; + int maxidx = -1; + for (int i = 0; i < si->num; ++i) { + slice_t *s = si->s[i]; + nvs_slice_param_t *p = s->algo_data; + _nvs_int_t *ip = s->int_data; + /* if this slice has been marked as inactive, disable to prevent that + * it's exp rate is uselessly driven down */ + if (!ip->active) + continue; + + float w = 0.0f; + if (p->type == NVS_RATE) { + float inst = 0.0f; + if (ip->rb > 0) { /* it was scheduled last round */ + /* inst rate: B in last round * 8(bit) / 1000000 (Mbps) * 1000 (1ms) */ + inst = (float) RC.mac[mod_id]->eNB_stats[CC_id].dlsch_bytes_tx * 8 / 1000; + ip->eff = (1.0f - ip->beta_eff) * ip->eff + ip->beta_eff * inst; + //LOG_W(MAC, "i %d slice %d ip->rb %d inst %f ip->eff %f\n", i, s->id, ip->rb, inst, ip->eff); + ip->rb = 0; + } + ip->exp = (1 - BETA) * ip->exp + BETA * inst; + const float rsv = p->Mbps_reserved * min(1.0f, ip->eff / p->Mbps_reference); + w = rsv / ip->exp; + } else { + float inst = (float)ip->rb / N_RB_DL; + ip->exp = (1.0f - BETA) * ip->exp + BETA * inst; + w = p->pct_reserved / ip->exp; + } + //LOG_I(MAC, "i %d slice %d type %d ip->exp %f w %f\n", i, s->id, p->type, ip->exp, w); + ip->rb = 0; + if (w > maxw + 0.001f) { + maxw = w; + maxidx = i; + } + } + + if (maxidx < 0) + return; + + int nb_rb = n_rbg_sched * RBGsize; + if (rbgalloc_mask[N_RBG - 1] + && (N_RB_DL == 15 || N_RB_DL == 25 || N_RB_DL == 50 || N_RB_DL == 75)) + nb_rb -= 1; + ((_nvs_int_t *)si->s[maxidx]->int_data)->rb = nb_rb; + + int rbg_rem = n_rbg_sched; + if (si->s[maxidx]->UEs.head >= 0) { + rbg_rem = si->s[maxidx]->dl_algo.run(mod_id, + CC_id, + frame, + subframe, + &si->s[maxidx]->UEs, + 4, // max_num_ue + n_rbg_sched, + rbgalloc_mask, + si->s[maxidx]->dl_algo.data); + } + if (rbg_rem == n_rbg_sched) // if no RBGs have been used mark as inactive + ((_nvs_int_t *)si->s[maxidx]->int_data)->active = 0; + + // the following block is meant for validation of the pre-processor to check + // whether all UE allocations are non-overlapping and is not necessary for + // scheduling functionality + char t[26] = "_________________________"; + t[N_RBG] = 0; + for (int i = 0; i < N_RBG; i++) + for (int j = 0; j < RBGsize; j++) + if (vrb_map[RBGsize*i+j] != 0) + t[i] = 'x'; + int print = 0; + for (int UE_id = UE_info->list.head; UE_id >= 0; UE_id = UE_info->list.next[UE_id]) { + const UE_sched_ctrl_t *ue_sched_ctrl = &UE_info->UE_sched_ctrl[UE_id]; + + if (ue_sched_ctrl->pre_nb_available_rbs[CC_id] == 0) + continue; + + LOG_D(MAC, + "%4d.%d UE%d %d RBs allocated, pre MCS %d\n", + frame, + subframe, + UE_id, + ue_sched_ctrl->pre_nb_available_rbs[CC_id], + UE_info->eNB_UE_stats[CC_id][UE_id].dlsch_mcs1); + + print = 1; + + for (int i = 0; i < N_RBG; i++) { + if (!ue_sched_ctrl->rballoc_sub_UE[CC_id][i]) + continue; + for (int j = 0; j < RBGsize; j++) { + if (vrb_map[RBGsize*i+j] != 0) { + LOG_I(MAC, "%4d.%d DL scheduler allocation list: %s\n", frame, subframe, t); + LOG_E(MAC, "%4d.%d: UE %d allocated at locked RB %d/RBG %d\n", frame, + subframe, UE_id, RBGsize * i + j, i); + } + vrb_map[RBGsize*i+j] = 1; + } + t[i] = '0' + UE_id; + } + } + if (print) + LOG_D(MAC, "%4d.%d DL scheduler allocation list: %s\n", frame, subframe, t); +} + +void nvs_ul(module_id_t mod_id, + int CC_id, + frame_t frame, + sub_frame_t subframe, + frame_t sched_frame, + sub_frame_t sched_subframe) { + ulsch_scheduler_pre_processor(mod_id, CC_id, frame, subframe, sched_frame, sched_subframe); +} + +void nvs_destroy(slice_info_t **si) { + const int n_dl = (*si)->num; + (*si)->num = 0; + for (int i = 0; i < n_dl; ++i) { + slice_t *s = (*si)->s[i]; + if (s->label) + free(s->label); + free(s->algo_data); + free(s->int_data); + free(s); + } + free((*si)->s); +} + +pp_impl_param_t nvs_dl_init(module_id_t mod_id, int CC_id) { + slice_info_t *si = calloc(1, sizeof(slice_info_t)); + DevAssert(si); + + si->num = 0; + si->s = calloc(MAX_NVS_SLICES, sizeof(slice_t)); + DevAssert(si->s); + for (int i = 0; i < MAX_MOBILES_PER_ENB; ++i) + si->UE_assoc_slice[i] = -1; + + /* insert default slice, all resources */ + nvs_slice_param_t *dlp = malloc(sizeof(nvs_slice_param_t)); + DevAssert(dlp); + dlp->type = NVS_RES; + dlp->pct_reserved = 1.0f; + default_sched_dl_algo_t *algo = &RC.mac[mod_id]->pre_processor_dl.dl_algo; + algo->data = NULL; + const int rc = addmod_nvs_slice_dl(si, 0, strdup("default"), algo, dlp); + DevAssert(0 == rc); + const UE_list_t *UE_list = &RC.mac[mod_id]->UE_info.list; + for (int UE_id = UE_list->head; UE_id >= 0; UE_id = UE_list->next[UE_id]) + slicing_add_UE(si, UE_id); + + pp_impl_param_t nvs; + nvs.algorithm = NVS_SLICING; + nvs.add_UE = slicing_add_UE; + nvs.remove_UE = slicing_remove_UE; + nvs.move_UE = slicing_move_UE; + nvs.addmod_slice = addmod_nvs_slice_dl; + nvs.remove_slice = remove_nvs_slice_dl; + nvs.dl = nvs_dl; + // current DL algo becomes default scheduler + nvs.dl_algo = *algo; + nvs.destroy = nvs_destroy; + nvs.slices = si; + + return nvs; +} diff --git a/openair2/LAYER2/MAC/slicing/slicing.h b/openair2/LAYER2/MAC/slicing/slicing.h index 1099b50ea0cb92533d14a1bfa57945de0deefcb9..e47973178ce39abf892257bfe5505983467ea062 100644 --- a/openair2/LAYER2/MAC/slicing/slicing.h +++ b/openair2/LAYER2/MAC/slicing/slicing.h @@ -71,4 +71,18 @@ pp_impl_param_t static_dl_init(module_id_t mod_id, int CC_id); pp_impl_param_t static_ul_init(module_id_t mod_id, int CC_id); +#define NVS_SLICING 20 +/* arbitrary upper limit, increase if you want to instantiate more slices */ +#define MAX_NVS_SLICES 10 +/* window for slice weight averaging -> 1s for fine granularity */ +#define BETA 0.001f +typedef struct { + enum nvs_type {NVS_RATE, NVS_RES} type; + union { + struct { float Mbps_reserved; float Mbps_reference; }; + struct { float pct_reserved; }; + }; +} nvs_slice_param_t; +pp_impl_param_t nvs_dl_init(module_id_t mod_id, int CC_id); + #endif /* __SLICING_H__ */