Commit 8ed54d70 authored by Maxime's avatar Maxime Committed by Maxime Elkael

Refactor DL scheduler into staged pipeline with pluggable function pointers

Assisted By Claude Code:Opus-4.6
Signed-off-by: default avatarMaxime Elkael <m.elkael@northeastern.edu>
parent 6fc2d060
...@@ -1271,6 +1271,7 @@ set (MAC_NR_SRC ...@@ -1271,6 +1271,7 @@ set (MAC_NR_SRC
${NR_GNB_MAC_DIR}/gNB_scheduler.c ${NR_GNB_MAC_DIR}/gNB_scheduler.c
${NR_GNB_MAC_DIR}/gNB_scheduler_bch.c ${NR_GNB_MAC_DIR}/gNB_scheduler_bch.c
${NR_GNB_MAC_DIR}/gNB_scheduler_dlsch.c ${NR_GNB_MAC_DIR}/gNB_scheduler_dlsch.c
${NR_GNB_MAC_DIR}/gNB_scheduler_dlsch_default_policies.c
${NR_GNB_MAC_DIR}/gNB_scheduler_ulsch.c ${NR_GNB_MAC_DIR}/gNB_scheduler_ulsch.c
${NR_GNB_MAC_DIR}/gNB_scheduler_primitives.c ${NR_GNB_MAC_DIR}/gNB_scheduler_primitives.c
${NR_GNB_MAC_DIR}/gNB_scheduler_phytest.c ${NR_GNB_MAC_DIR}/gNB_scheduler_phytest.c
......
...@@ -269,7 +269,22 @@ void nr_dlsim_preprocessor(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pp_pdsch) ...@@ -269,7 +269,22 @@ void nr_dlsim_preprocessor(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pp_pdsch)
AssertFatal(sched_pdsch.mcs >= 0, "invalid mcs %d\n", sched_pdsch.mcs); AssertFatal(sched_pdsch.mcs >= 0, "invalid mcs %d\n", sched_pdsch.mcs);
AssertFatal(current_BWP->mcsTableIdx >= 0 && current_BWP->mcsTableIdx <= 2, "invalid mcsTableIdx %d\n", current_BWP->mcsTableIdx); AssertFatal(current_BWP->mcsTableIdx >= 0 && current_BWP->mcsTableIdx <= 2, "invalid mcsTableIdx %d\n", current_BWP->mcsTableIdx);
post_process_dlsch(nr_mac, pp_pdsch, UE_info, &sched_pdsch); nr_dl_candidate_t candidate = {
.UE = UE_info,
.rnti = UE_info->rnti,
.is_retx = sched_ctrl->harq_processes[sched_pdsch.dl_harq_pid].round > 0,
.retx_harq_pid = sched_pdsch.dl_harq_pid,
.pending_bytes = sched_ctrl->num_total_bytes,
.mcs_table = current_BWP->mcsTableIdx,
.bwp_start = sched_pdsch.bwp_info.bwpStart,
.bwp_size = sched_pdsch.bwp_info.bwpSize,
};
for (int i = 0; i < seq_arr_size(&sched_ctrl->lc_config); ++i) {
const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, i);
candidate.pending_bytes_per_lcid[c->lcid] = sched_ctrl->rlc_status[c->lcid].bytes_in_buffer;
}
post_process_dlsch(nr_mac, pp_pdsch, UE_info, &sched_pdsch, &candidate);
} }
nrUE_params_t nrUE_params; nrUE_params_t nrUE_params;
......
...@@ -404,164 +404,6 @@ bwp_info_t get_pdsch_bwp_start_size(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE) ...@@ -404,164 +404,6 @@ bwp_info_t get_pdsch_bwp_start_size(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE)
return bwp_info; return bwp_info;
} }
static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
post_process_pdsch_t *pp_pdsch,
int *n_rb_sched,
NR_UE_info_t *UE,
int beam_idx,
int current_harq_pid)
{
frame_t frame = pp_pdsch->frame;
slot_t slot = pp_pdsch->slot;
int CC_id = 0;
const NR_ServingCellConfigCommon_t *scc = nr_mac->common_channels->ServingCellConfigCommon;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch;
int layers = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
int pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP);
// If the RI changed between current rtx and a previous transmission
// we need to verify if it is not decreased
// othwise it wouldn't be possible to transmit the same TBS
layers = (layers < new_sched.nrOfLayers) ? layers : new_sched.nrOfLayers;
pm_index = (layers < new_sched.nrOfLayers) ? pm_index : new_sched.pm_index;
const int coresetid = sched_ctrl->coreset->controlResourceSetId;
const int tda = get_dl_tda(nr_mac, slot);
AssertFatal(tda >= 0,"Unable to find PDSCH time domain allocation in list\n");
/* Check first whether the old TDA can be reused
* this helps allocate retransmission when TDA changes (e.g. new nrOfSymbols > old nrOfSymbols) */
NR_tda_info_t temp_tda = get_dl_tda_info(dl_bwp,
sched_ctrl->search_space->searchSpaceType->present,
tda,
scc->dmrs_TypeA_Position,
1,
TYPE_C_RNTI_,
coresetid,
false);
if (!temp_tda.valid_tda)
return false;
bool reuse_old_tda = (new_sched.tda_info.startSymbolIndex == temp_tda.startSymbolIndex) && (new_sched.tda_info.nrOfSymbols <= temp_tda.nrOfSymbols);
LOG_D(NR_MAC, "[UE %x] %s old TDA, %s number of layers\n",
UE->rnti,
reuse_old_tda ? "reuse" : "do not reuse",
layers == new_sched.nrOfLayers ? "same" : "different");
uint16_t *rballoc_mask = nr_mac->common_channels[CC_id].vrb_map[beam_idx];
bwp_info_t bwp_info = get_pdsch_bwp_start_size(nr_mac, UE);
const int bwp_start = bwp_info.bwpStart;
const int bwp_size = bwp_info.bwpSize;
// WRT the BWP start, the RBs are indexed from 0 to bwpSize-1
int rbStart = 0;
int rbSize = 0;
if (reuse_old_tda && layers == new_sched.nrOfLayers) {
/* Check that there are enough resources for retransmission */
const uint16_t slbitmap = SL_to_bitmap(new_sched.tda_info.startSymbolIndex, new_sched.tda_info.nrOfSymbols);
if (!get_rb_alloc(new_sched.rbSize, new_sched.rbSize, bwp_start, bwp_size, rballoc_mask, slbitmap, &rbStart, &rbSize)) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not allocate DL retransmission: no resources\n", UE->rnti, frame, slot);
return false;
}
} else {
/* the retransmission will use a different time domain allocation, check
* that we have enough resources */
NR_pdsch_dmrs_t temp_dmrs = get_dl_dmrs_params(scc, dl_bwp, &temp_tda, layers);
uint32_t new_tbs;
uint16_t new_rbSize;
bool success = nr_find_nb_rb(new_sched.Qm,
new_sched.R,
1, // no transform precoding for DL
layers,
temp_tda.nrOfSymbols,
temp_dmrs.N_PRB_DMRS * temp_dmrs.N_DMRS_SLOT,
new_sched.tb_size,
1, /* minimum of 1RB: need to find exact TBS, don't preclude any number */
bwp_size,
&new_tbs,
&new_rbSize);
if (!success || new_tbs != new_sched.tb_size) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] allocation of DL retransmission failed: new TBS %d of new TDA does not match old TBS %d\n",
UE->rnti,
frame,
slot,
new_tbs,
new_sched.tb_size);
return false; /* the maximum TBsize we might have is smaller than what we need */
}
const uint16_t slbitmap = SL_to_bitmap(temp_tda.startSymbolIndex, temp_tda.nrOfSymbols);
if (!get_rb_alloc(new_rbSize, new_rbSize, bwp_start, bwp_size, rballoc_mask, slbitmap, &rbStart, &rbSize)) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not allocate DL retransmission: no resources\n", UE->rnti, frame, slot);
return false;
}
/* we can allocate it. Overwrite the time_domain_allocation, the number
* of RBs, and the new TB size. The rest is done below */
new_sched.tb_size = new_tbs;
new_sched.rbSize = new_rbSize;
new_sched.time_domain_allocation = tda;
new_sched.nrOfLayers = layers;
new_sched.pm_index = pm_index;
new_sched.dmrs_parms = temp_dmrs;
new_sched.tda_info = temp_tda;
}
/* Find a free CCE */
int CCEIndex = get_cce_index(nr_mac,
CC_id,
slot,
UE->rnti,
&sched_ctrl->aggregation_level,
beam_idx,
sched_ctrl->search_space,
sched_ctrl->coreset,
&sched_ctrl->sched_pdcch,
sched_ctrl->pdcch_cl_adjust);
if (CCEIndex<0) {
sched_ctrl->dl_cce_fail++;
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find free CCE for DL DCI retransmission\n", UE->rnti, frame, slot);
return false;
}
/* Find PUCCH occasion: if it fails, undo CCE allocation (undoing PUCCH
* allocation after CCE alloc fail would be more complex) */
int alloc = -1;
if (!get_FeedbackDisabled(UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, current_harq_pid)) {
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
alloc = nr_acknack_scheduling(nr_mac, UE, frame, slot, UE->UE_beam_index, r_pucch, 0);
if (alloc < 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI retransmission\n", UE->rnti, frame, slot);
return false;
}
}
sched_ctrl->cce_index = CCEIndex;
fill_pdcch_vrb_map(nr_mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam_idx);
new_sched.rbStart = rbStart;
new_sched.pucch_allocation = alloc;
new_sched.bwp_info = bwp_info;
post_process_dlsch(nr_mac, pp_pdsch, UE, &new_sched);
/* retransmissions: directly allocate */
*n_rb_sched -= new_sched.rbSize;
for (int rb = new_sched.bwp_info.bwpStart + new_sched.rbStart; rb < new_sched.bwp_info.bwpStart + new_sched.rbStart + new_sched.rbSize; rb++)
rballoc_mask[rb] |= SL_to_bitmap(new_sched.tda_info.startSymbolIndex, new_sched.tda_info.nrOfSymbols);
return true;
}
static void ack_reconfig(gNB_MAC_INST *mac, NR_UE_info_t *UE) static void ack_reconfig(gNB_MAC_INST *mac, NR_UE_info_t *UE)
{ {
if (!UE->reconfigCellGroup) { if (!UE->reconfigCellGroup) {
...@@ -590,308 +432,438 @@ static bool dlsch_to_schedule(const NR_UE_sched_ctrl_t *sched_ctrl) ...@@ -590,308 +432,438 @@ static bool dlsch_to_schedule(const NR_UE_sched_ctrl_t *sched_ctrl)
return false; return false;
} }
typedef struct UEsched_s { static int collect_dl_candidates(gNB_MAC_INST *mac,
float coef; NR_UE_info_t **UE_list,
NR_UE_info_t * UE; nr_dl_candidate_t *candidates,
int selected_mcs; int max_candidates,
} UEsched_t; frame_t frame,
slot_t slot)
static int comparator(const void *p, const void *q)
{
const UEsched_t *pp = p;
const UEsched_t *qq = q;
if (pp->coef < qq->coef)
return 1;
else if (pp->coef > qq->coef)
return -1;
return 0;
}
static void pf_dl(gNB_MAC_INST *mac,
post_process_pdsch_t *pp_pdsch,
NR_UE_info_t **UE_list,
int max_num_ue,
int num_beams,
int n_rb_sched[num_beams])
{ {
frame_t frame = pp_pdsch->frame; int n = 0;
slot_t slot = pp_pdsch->slot; const frame_structure_t *fs = &mac->frame_structure;
const float dl_slots_per_s = (float)get_dl_slots_per_period(fs) / fs->numb_slots_period * fs->numb_slots_frame * 100;
NR_ServingCellConfigCommon_t *scc=mac->common_channels[0].ServingCellConfigCommon; UE_iterator (UE_list, UE) {
// UEs that could be scheduled if (n >= max_candidates)
UEsched_t UE_sched[MAX_MOBILES_PER_GNB + 1] = {0}; break;
int remainUEs[num_beams];
for (int i = 0; i < num_beams; i++)
remainUEs[i] = max_num_ue;
int numUE = 0;
int CC_id = 0;
int slots_per_frame = mac->frame_structure.numb_slots_frame;
/* Loop UE_info->list to check retransmission */ /* Update EWMA and reset current_bytes before the active check so inactive
UE_iterator(UE_list, UE) { * UEs don't get stuck with stale byte counts. */
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; NR_mac_dir_stats_t *stats = &UE->mac_stats.dl;
NR_UE_DL_BWP_t *current_BWP = &UE->current_DL_BWP; float instant_bps = (float)stats->current_bytes * 8.0f * dl_slots_per_s;
UE->dl_thr_ue = (1 - 0.01f) * UE->dl_thr_ue + 0.01f * instant_bps;
UE->dl_thr_ue_display = (1 - 0.001f) * UE->dl_thr_ue_display + 0.001f * instant_bps;
stats->current_bytes = 0;
stats->current_rbs = 0;
if (!nr_mac_ue_is_active(UE)) if (!nr_mac_ue_is_active(UE))
continue; continue;
NR_mac_dir_stats_t *stats = &UE->mac_stats.dl; NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
/* get the PID of a HARQ process awaiting retrnasmission, or -1 otherwise */ NR_UE_DL_BWP_t *current_BWP = &UE->current_DL_BWP;
int harq_pid = sched_ctrl->retrans_dl_harq.head;
/* Calculate Throughput */
const float a = 0.01f;
const uint32_t b = stats->current_bytes;
UE->dl_thr_ue = (1 - a) * UE->dl_thr_ue + a * b;
stats->current_bytes = 0;
stats->current_rbs = 0;
/* Check if this UE should get TA. If we add the CE, ta_apply will be reset */ /* Check TA */
if (frame == sched_ctrl->ta_frame) if (frame == sched_ctrl->ta_frame)
sched_ctrl->ta_apply = true; sched_ctrl->ta_apply = true;
int total_rem_ues = 0; int harq_pid = sched_ctrl->retrans_dl_harq.head;
for (int i = 0; i < num_beams; i++) const NR_bler_options_t *bo = &mac->dl_bler;
total_rem_ues += remainUEs[i]; bwp_info_t bwp_info = get_pdsch_bwp_start_size(mac, UE);
if (total_rem_ues == 0) const int max_mcs_table = current_BWP->mcsTableIdx == 1 ? 27 : 28;
continue; const int max_mcs = min(sched_ctrl->dl_max_mcs, min(max_mcs_table, bo->max_mcs));
/* retransmission */ /* QoS / slice info — extract from first DRB for external policies */
if (harq_pid >= 0) { uint64_t fiveQI = 0;
NR_beam_alloc_t beam = beam_allocation_procedure(&mac->beam_info, frame, slot, UE->UE_beam_index, slots_per_frame); int lc_priority = 0;
bool sch_ret = beam.idx >= 0; nssai_t nssai = {0, 0xFFFFFF};
/* Allocate retransmission */ for (int j = 0; j < seq_arr_size(&sched_ctrl->lc_config); j++) {
if (sch_ret) const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, j);
sch_ret = allocate_dl_retransmission(mac, pp_pdsch, &n_rb_sched[beam.idx], UE, beam.idx, harq_pid); if (c->lcid >= 4) {
if (!sch_ret) { lc_priority = c->priority;
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] DL retransmission could not be allocated\n", UE->rnti, frame, slot); nssai = c->nssai;
reset_beam_status(&mac->beam_info, frame, slot, UE->UE_beam_index, slots_per_frame, beam.new_beam); for (int q = 0; q < NR_MAX_NUM_QFI; q++) {
continue; if (c->qos_config[q].fiveQI > 0) {
fiveQI = c->qos_config[q].fiveQI;
break;
}
}
if (fiveQI > 0)
break;
} }
/* reduce max_num_ue once we are sure UE can be allocated, i.e., has CCE */ }
remainUEs[beam.idx]--; uint16_t cqi = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.wb_cqi_1tb;
uint8_t csi_ri = (current_BWP->dci_format == NR_DL_DCI_FORMAT_1_0) ? 0 : sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri;
int csi_pm_index = get_pm_index(mac, UE, current_BWP->dci_format, csi_ri + 1, mac->radio_config.pdsch_AntennaPorts.XP);
if (harq_pid >= 0) {
/* retransmission candidate */
const NR_UE_harq_t *harq = &sched_ctrl->harq_processes[harq_pid];
candidates[n++] = (nr_dl_candidate_t){
/* identity / scheduling state */
.UE = UE,
.rnti = UE->rnti,
.is_retx = true,
.retx_harq_pid = harq_pid,
.retx_rbSize = harq->sched_pdsch.rbSize,
.avg_throughput = UE->dl_thr_ue,
.bler = sched_ctrl->dl_bler_stats.bler,
.current_mcs = harq->sched_pdsch.mcs,
.max_mcs = max_mcs,
.mcs_table = current_BWP->mcsTableIdx,
.bwp_start = bwp_info.bwpStart,
.bwp_size = bwp_info.bwpSize,
.fiveQI = fiveQI,
.priority = lc_priority,
.nssai = nssai,
/* CSI observations */
.cqi = cqi,
.csi_ri = csi_ri,
.csi_pm_index = csi_pm_index,
.beam_rsrp = UE->beam_rsrp,
.beam_sinr = UE->beam_sinr,
/* alloc decisions — zeroed here, written by pipeline stages */
.sched_pdsch = {0},
.alloc_beam_dir = UE->UE_beam_index,
.alloc_beam_idx = 0,
.alloc_new_beam = false,
};
} else { } else {
/* skip this UE if there are no free HARQ processes. This can happen e.g. /* new transmission candidate */
* if the UE disconnected in L2sim, in which case the gNB is not notified if (sched_ctrl->available_dl_harq.head < 0)
* (this can be considered a design flaw) */
if (sched_ctrl->available_dl_harq.head < 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] UE has no free DL HARQ process, skipping\n",
UE->rnti,
frame,
slot);
continue; continue;
}
update_dlsch_buffer(pp_pdsch->frame, pp_pdsch->slot, UE); update_dlsch_buffer(frame, slot, UE);
if (!dlsch_to_schedule(sched_ctrl)) if (!dlsch_to_schedule(sched_ctrl))
continue; continue;
/* Calculate coeff */ /* Update BLER stats; MCS adaptation is done by dl_mcs_select for all candidates. */
const NR_bler_options_t *bo = &mac->dl_bler; bool bler_updated = update_bler_stats(bo, stats, &sched_ctrl->dl_bler_stats, frame);
const int max_mcs_table = current_BWP->mcsTableIdx == 1 ? 27 : 28;
const int max_mcs = min(sched_ctrl->dl_max_mcs, max_mcs_table); candidates[n++] = (nr_dl_candidate_t){
int selected_mcs; /* identity / scheduling state */
if (bo->harq_round_max == 1) { .UE = UE,
int new_mcs = min(bo->max_mcs, max_mcs); .rnti = UE->rnti,
selected_mcs = max(bo->min_mcs, new_mcs); .is_retx = false,
sched_ctrl->dl_bler_stats.mcs = selected_mcs; .retx_harq_pid = -1,
} else .pending_bytes = sched_ctrl->num_total_bytes,
selected_mcs = get_mcs_from_bler(bo, stats, &sched_ctrl->dl_bler_stats, max_mcs, frame); .avg_throughput = UE->dl_thr_ue,
int l = get_dl_nrOfLayers(sched_ctrl, current_BWP->dci_format); .bler = sched_ctrl->dl_bler_stats.bler,
const uint8_t Qm = nr_get_Qm_dl(selected_mcs, current_BWP->mcsTableIdx); .current_mcs = min(sched_ctrl->dl_bler_stats.mcs, max_mcs),
const uint16_t R = nr_get_code_rate_dl(selected_mcs, current_BWP->mcsTableIdx); .max_mcs = max_mcs,
uint32_t tbs = nr_compute_tbs(Qm, .last_num_sched = sched_ctrl->dl_bler_stats.last_num_sched,
R, .bler_updated = bler_updated,
1, /* rbSize */ .mcs_table = current_BWP->mcsTableIdx,
10, /* hypothetical number of slots */ .bwp_start = bwp_info.bwpStart,
0, /* N_PRB_DMRS * N_DMRS_SLOT */ .bwp_size = bwp_info.bwpSize,
0 /* N_PRB_oh, 0 for initialBWP */, .fiveQI = fiveQI,
0 /* tb_scaling */, .priority = lc_priority,
l) >> 3; .nssai = nssai,
float coeff_ue = (float) tbs / UE->dl_thr_ue; /* CSI observations */
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] b %d, thr_ue %f, tbs %d, coeff_ue %f\n", .cqi = cqi,
UE->rnti, .csi_ri = csi_ri,
frame, .csi_pm_index = csi_pm_index,
slot, .beam_rsrp = UE->beam_rsrp,
b, .beam_sinr = UE->beam_sinr,
UE->dl_thr_ue, /* alloc decisions — zeroed here, written by pipeline stages */
tbs, .sched_pdsch = {0},
coeff_ue); .alloc_beam_dir = UE->UE_beam_index,
/* Create UE_sched list for UEs eligible for new transmission*/ .alloc_beam_idx = 0,
UE_sched[numUE].coef = coeff_ue; .alloc_new_beam = false,
UE_sched[numUE].UE = UE; };
UE_sched[numUE].selected_mcs = selected_mcs; nr_dl_candidate_t *c = &candidates[n - 1];
numUE++; for (int lcid = 0; lcid < NR_MAX_NUM_LCID; lcid++)
c->pending_bytes_per_lcid[lcid] = sched_ctrl->rlc_status[lcid].bytes_in_buffer;
} }
} }
qsort(UE_sched, numUE, sizeof(UEsched_t), comparator); return n;
UEsched_t *iterator = UE_sched; }
/* ---- PF weight utility (used by default beam_select and rb_alloc policies) ---- */
float dl_pf_weight(int mcs, int mcs_table, int nrOfLayers, float avg_throughput)
{
uint8_t Qm = nr_get_Qm_dl(mcs, mcs_table);
uint16_t R = nr_get_code_rate_dl(mcs, mcs_table);
uint32_t tbs = nr_compute_tbs(Qm, R, 1, 10, 0, 0, 0, nrOfLayers) >> 3;
return (float)tbs / fmaxf(avg_throughput, 1e-9f);
}
static int compare_beam_idx(const void *a, const void *b)
{
return ((const nr_dl_candidate_t *)a)->alloc_beam_idx - ((const nr_dl_candidate_t *)b)->alloc_beam_idx;
}
const int min_rbSize = 5; /* Check retx feasibility against a new TDA.
* Refits rbSize via nr_find_nb_rb so the new TDA preserves TBS.
* Returns the new rbSize needed (>0), or 0 if infeasible. */
uint16_t check_dl_retx_feasibility(const nr_dl_candidate_t *cand,
int tda,
const NR_tda_info_t *tda_info,
const NR_ServingCellConfigCommon_t *scc,
uint16_t max_rbSize)
{
NR_UE_sched_ctrl_t *sched_ctrl = &cand->UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *dl_bwp = &cand->UE->current_DL_BWP;
const NR_sched_pdsch_t *orig = &sched_ctrl->harq_processes[cand->retx_harq_pid].sched_pdsch;
NR_pdsch_dmrs_t dmrs = get_dl_dmrs_params(scc, dl_bwp, tda_info, orig->nrOfLayers);
uint32_t new_tbs;
uint16_t new_rbSize;
bool ok = nr_find_nb_rb(orig->Qm,
orig->R,
1,
orig->nrOfLayers,
tda_info->nrOfSymbols,
dmrs.N_PRB_DMRS * dmrs.N_DMRS_SLOT,
orig->tb_size,
1,
max_rbSize,
&new_tbs,
&new_rbSize);
if (!ok || new_tbs != orig->tb_size) {
LOG_D(NR_MAC, "[UE %04x] retx TDA change infeasible: new TBS %d != old TBS %d\n", cand->rnti, new_tbs, orig->tb_size);
return 0;
}
LOG_D(NR_MAC,
"retx TDA change %d->%d feasible: rbSize %d->%d (TBS %d preserved)\n",
orig->time_domain_allocation,
tda,
orig->rbSize,
new_rbSize,
orig->tb_size);
return new_rbSize;
}
/* Loop UE_sched to find max coeff and allocate transmission */ /* ---- CCE/PUCCH validation, called from inside the scheduling policy ---- */
while (iterator->UE != NULL) {
NR_UE_sched_ctrl_t *sched_ctrl = &iterator->UE->UE_sched_ctrl; bool nr_dl_validate_cce_pucch(const nr_dl_sched_params_t *params, nr_dl_candidate_t *cand)
const uint16_t rnti = iterator->UE->rnti; {
NR_UE_info_t *UE = cand->UE;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *dl_bwp = &iterator->UE->current_DL_BWP; int agg_level = sched_ctrl->aggregation_level;
NR_UE_UL_BWP_t *ul_bwp = &iterator->UE->current_UL_BWP; NR_sched_pdcch_t sched_pdcch = sched_ctrl->sched_pdcch;
int CCEIndex = get_cce_index(params->mac,
params->CC_id,
params->slot,
UE->rnti,
&agg_level,
cand->alloc_beam_idx,
sched_ctrl->search_space,
sched_ctrl->coreset,
&sched_pdcch,
sched_ctrl->pdcch_cl_adjust);
if (CCEIndex < 0) {
sched_ctrl->dl_cce_fail++;
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find free CCE for DL DCI\n", UE->rnti, params->frame, params->slot);
return false;
}
if (sched_ctrl->available_dl_harq.head < 0) { int harq_pid = cand->is_retx ? cand->retx_harq_pid : sched_ctrl->available_dl_harq.head;
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] UE has no free DL HARQ process, skipping\n", int pucch_alloc = -1;
iterator->UE->rnti, if (!get_FeedbackDisabled(UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, harq_pid)) {
frame, NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
slot); int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
iterator++; pucch_alloc = nr_acknack_scheduling(params->mac, UE, params->frame, params->slot, UE->UE_beam_index, r_pucch, 0);
continue; if (pucch_alloc < 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI\n", UE->rnti, params->frame, params->slot);
return false;
} }
}
NR_beam_alloc_t beam = beam_allocation_procedure(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame); cand->alloc_cce_index = CCEIndex;
cand->alloc_aggregation_level = agg_level;
cand->sched_pdsch.pucch_allocation = pucch_alloc;
cand->alloc_sched_pdcch = sched_pdcch;
return true;
}
if (beam.idx < 0) { // Validate CCE/PUCCH and mark VRBs for a candidate whose sched_pdsch.rbStart/rbSize/mcs
// no available beam // have already been set. Returns false if validation fails (UE should be skipped).
iterator++; bool commit_alloc(const nr_dl_sched_params_t *params, nr_dl_candidate_t *cand)
continue; {
} if (!nr_dl_validate_cce_pucch(params, cand))
if (remainUEs[beam.idx] == 0 || n_rb_sched[beam.idx] < min_rbSize) { return false;
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam);
iterator++;
continue;
}
/* MCS has been set above */ int beam = cand->alloc_beam_idx;
int tda = get_dl_tda(mac, slot);
AssertFatal(tda >= 0,"Unable to find PDSCH time domain allocation in list\n");
const int coresetid = sched_ctrl->coreset->controlResourceSetId;
NR_tda_info_t tda_info = get_dl_tda_info(dl_bwp,
sched_ctrl->search_space->searchSpaceType->present,
tda,
scc->dmrs_TypeA_Position,
1,
TYPE_C_RNTI_,
coresetid,
false);
AssertFatal(tda_info.valid_tda, "Invalid TDA from get_dl_tda_info\n");
const uint16_t slbitmap = SL_to_bitmap(tda_info.startSymbolIndex, tda_info.nrOfSymbols);
uint16_t *rballoc_mask = mac->common_channels[CC_id].vrb_map[beam.idx];
bwp_info_t bwp_info = get_pdsch_bwp_start_size(mac, iterator->UE);
int rbStart = 0; // WRT BWP start
int bwp_start = bwp_info.bwpStart;
int bwp_size = bwp_info.bwpSize;
// Freq-demain allocation
int max_rbSize = 0;
if (!get_rb_alloc(min_rbSize, bwp_size, bwp_start, bwp_size, rballoc_mask, slbitmap, &rbStart, &max_rbSize)) {
LOG_D(NR_MAC,
"(%d.%d) Cannot schedule RNTI %04x, rbStart %d, rbSize %d\n",
frame,
slot,
rnti,
rbStart,
max_rbSize);
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam);
iterator++;
continue;
}
int CCEIndex = get_cce_index(mac, /* Mark CCE as used so subsequent UEs in the same slot see it as taken */
CC_id, fill_pdcch_vrb_map(params->mac,
slot, params->CC_id,
iterator->UE->rnti, &cand->alloc_sched_pdcch,
&sched_ctrl->aggregation_level, cand->alloc_cce_index,
beam.idx, cand->alloc_aggregation_level,
sched_ctrl->search_space, beam);
sched_ctrl->coreset,
&sched_ctrl->sched_pdcch, uint16_t *vrb_map = params->vrb_map[beam];
sched_ctrl->pdcch_cl_adjust); for (int rb = 0; rb < cand->sched_pdsch.rbSize; rb++)
if (CCEIndex < 0) { vrb_map[cand->sched_pdsch.rbStart + rb + cand->bwp_start] |= cand->alloc_slbitmap;
sched_ctrl->dl_cce_fail++;
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find free CCE for DL DCI\n", rnti, frame, slot); return true;
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam); }
iterator++;
static void nr_dl_schedule(gNB_MAC_INST *mac,
post_process_pdsch_t *pp_pdsch,
NR_UE_info_t **UE_list,
int max_num_ue,
int num_beams,
int n_rb_sched[num_beams])
{
frame_t frame = pp_pdsch->frame;
slot_t slot = pp_pdsch->slot;
int CC_id = 0;
NR_ServingCellConfigCommon_t *scc = mac->common_channels[CC_id].ServingCellConfigCommon;
int slots_per_frame = mac->frame_structure.numb_slots_frame;
/* Step 1: Collect candidates */
nr_dl_candidate_t candidates[MAX_MOBILES_PER_GNB] = {0};
int n = collect_dl_candidates(mac, UE_list, candidates, MAX_MOBILES_PER_GNB, frame, slot);
if (n == 0)
return;
/* Step 2: RI/PMI selection — sets sched_pdsch.nrOfLayers and pm_index per candidate */
mac->dl_ri_pmi_select(mac, candidates, n);
/* Step 3: Beam allocation (skip for single beam — candidates default to beam 0).
* Done before TDA so that TDA selection can use the allocated beam to check
* the correct VRB map and pick the best TDA per beam. */
FOR_EACH_CANDIDATE(cand, candidates, n)
cand->skipped = false;
if (num_beams > 1) {
int n_beam_valid = mac->dl_beam_select(&mac->beam_info, mac->beam_index_list, candidates, n, frame, slot, slots_per_frame);
if (n_beam_valid == 0)
return;
}
/* Step 4: Per-UE TDA selection. Resolves tda/tda_info/slbitmap on each
* candidate, marks invalids with skipped=true. */
int n_valid = mac->dl_tda_select(mac, candidates, n, frame, slot);
if (n_valid == 0)
return;
/* Step 5: MCS adaptation — sets sched_pdsch.mcs from BLER state for all candidates.
* Placed after beam allocation so a custom dl_mcs_select can factor in alloc_beam_dir
* (e.g. use beam RSRP/SINR to bias the MCS target). Also persists the decision so
* BLER-based MCS ramps even for candidates the policy rejects this slot. */
mac->dl_mcs_select(mac, candidates, n);
/* Step 6: Sort by beam, then call RB allocation policy per beam */
qsort(candidates, n, sizeof(*candidates), compare_beam_idx);
nr_dl_sched_params_t params = {
.mac = mac,
.CC_id = CC_id,
.frame = frame,
.slot = slot,
.num_beams = num_beams,
.max_num_ue = max_num_ue,
.min_mcs = mac->dl_bler.min_mcs,
.bler_lower = mac->dl_bler.lower,
.bler_upper = mac->dl_bler.upper,
};
for (int b = 0; b < num_beams; b++) {
params.vrb_map[b] = mac->common_channels[CC_id].vrb_map[b];
params.n_rb_avail[b] = n_rb_sched[b];
}
int i = 0;
while (i < n) {
int beam = candidates[i].alloc_beam_idx;
int start = i;
while (i < n && candidates[i].alloc_beam_idx == beam)
i++;
int count = i - start;
mac->dl_rb_alloc(&params, candidates + start, count);
}
/* Release beam reservations for candidates the policy rejected (failed
* CCE/PUCCH validation, no free RBs, max_num_ue reached, etc.).
* No-op in single-beam mode where alloc_new_beam is always false. */
for (int i = 0; i < n; i++) {
if (!candidates[i].scheduled && !candidates[i].skipped)
reset_beam_status(&mac->beam_info, frame, slot, candidates[i].alloc_beam_dir, slots_per_frame, candidates[i].alloc_new_beam);
}
/* Step 7: Persist MCS, apply CCE/PUCCH, compute TBS, post_process */
for (int j = 0; j < n; j++) {
nr_dl_candidate_t *cand = &candidates[j];
if (!cand->scheduled)
continue; continue;
} NR_UE_info_t *UE = cand->UE;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
/* Find PUCCH occasion: if it fails, undo CCE allocation (undoing PUCCH NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
* allocation after CCE alloc fail would be more complex) */
/* Reuse CCE/PUCCH from commit_alloc — no recomputation */
int alloc = -1; sched_ctrl->cce_index = cand->alloc_cce_index;
if (!get_FeedbackDisabled(iterator->UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, sched_ctrl->available_dl_harq.head)) { sched_ctrl->aggregation_level = cand->alloc_aggregation_level;
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex); sched_ctrl->sched_pdcch = cand->alloc_sched_pdcch;
alloc = nr_acknack_scheduling(mac, iterator->UE, frame, slot, iterator->UE->UE_beam_index, r_pucch, 0); /* fill_pdcch_vrb_map already called in commit_alloc */
if (alloc < 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI\n", rnti, frame, slot); bwp_info_t bwp_info = {.bwpStart = cand->bwp_start, .bwpSize = cand->bwp_size};
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam);
iterator++; /* Start from the candidate's sched_pdsch (pipeline stages already set
continue; * mcs, rbStart, rbSize, nrOfLayers, pm_index, tda, tda_info, pucch_allocation).
} * Fill remaining dispatch-only fields. */
NR_sched_pdsch_t sched_pdsch = cand->sched_pdsch;
sched_pdsch.bwp_info = bwp_info;
if (cand->is_retx) {
/* Retransmission: merge HARQ state (R, Qm, tb_size) with policy placement.
* DMRS must match the current TDA (recomputed by dl_tda_select when TDA
* changed; original otherwise). */
NR_sched_pdsch_t harq_pdsch = sched_ctrl->harq_processes[cand->retx_harq_pid].sched_pdsch;
sched_pdsch.R = harq_pdsch.R;
sched_pdsch.Qm = harq_pdsch.Qm;
sched_pdsch.tb_size = harq_pdsch.tb_size;
sched_pdsch.dl_harq_pid = cand->retx_harq_pid;
bool tda_changed = sched_pdsch.tda_info.startSymbolIndex != harq_pdsch.tda_info.startSymbolIndex
|| sched_pdsch.tda_info.nrOfSymbols != harq_pdsch.tda_info.nrOfSymbols;
if (tda_changed)
sched_pdsch.dmrs_parms = get_dl_dmrs_params(scc, dl_bwp, &sched_pdsch.tda_info, sched_pdsch.nrOfLayers);
else
sched_pdsch.dmrs_parms = harq_pdsch.dmrs_parms;
} else {
/* New transmission: compute TBS-related fields */
int l = sched_pdsch.nrOfLayers;
int mcs = sched_pdsch.mcs;
uint8_t Qm = nr_get_Qm_dl(mcs, dl_bwp->mcsTableIdx);
uint16_t R = nr_get_code_rate_dl(mcs, dl_bwp->mcsTableIdx);
NR_pdsch_dmrs_t dmrs = get_dl_dmrs_params(scc, dl_bwp, &sched_pdsch.tda_info, l);
sched_pdsch.R = R;
sched_pdsch.Qm = Qm;
sched_pdsch.dl_harq_pid = sched_ctrl->available_dl_harq.head;
sched_pdsch.dmrs_parms = dmrs;
/* Compute actual TBS (policy allocated max rbSize, nr_find_nb_rb gives actual) */
const int oh = 3 * 4 + (sched_ctrl->ta_apply ? 2 : 0);
nr_find_nb_rb(Qm,
R,
1,
l,
sched_pdsch.tda_info.nrOfSymbols,
dmrs.N_PRB_DMRS * dmrs.N_DMRS_SLOT,
sched_ctrl->num_total_bytes + oh,
5,
sched_pdsch.rbSize,
&sched_pdsch.tb_size,
&sched_pdsch.rbSize);
sched_pdsch.action = NULL;
int srb1 = 1;
if (UE->reconfigCellGroup && sched_ctrl->rlc_status[srb1].bytes_in_buffer > 10)
sched_pdsch.action = ack_reconfig;
} }
sched_ctrl->cce_index = CCEIndex; post_process_dlsch(mac, pp_pdsch, UE, &sched_pdsch, cand);
fill_pdcch_vrb_map(mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam.idx);
int l = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
NR_sched_pdsch_t sched_pdsch = {
// rbSize below
.rbStart = rbStart,
.mcs = iterator->selected_mcs,
.R = nr_get_code_rate_dl(iterator->selected_mcs, dl_bwp->mcsTableIdx),
.Qm = nr_get_Qm_dl(iterator->selected_mcs, dl_bwp->mcsTableIdx),
// tb_size below
.dl_harq_pid = sched_ctrl->available_dl_harq.head,
.pucch_allocation = alloc,
.pm_index = get_pm_index(mac, iterator->UE, dl_bwp->dci_format, l, mac->radio_config.pdsch_AntennaPorts.XP),
.nrOfLayers = l,
.bwp_info = bwp_info,
.dmrs_parms = get_dl_dmrs_params(scc, dl_bwp, &tda_info, l),
.time_domain_allocation = tda,
.tda_info = tda_info,
};
sched_pdsch.action = NULL;
int srb1 = 1;
/* everything that's only 3 bytes is an ack. To be safe, use a bit more. */
if (iterator->UE->reconfigCellGroup && sched_ctrl->rlc_status[srb1].bytes_in_buffer > 10)
sched_pdsch.action = ack_reconfig;
// Fix me: currently, the RLC does not give us the total number of PDUs
// awaiting. Therefore, for the time being, we put a fixed overhead of 12
// (for 4 PDUs) and optionally + 2 for TA. Once RLC gives the number of
// PDUs, we replace with 3 * numPDUs
const int oh = 3 * 4 + (sched_ctrl->ta_apply ? 2 : 0);
nr_find_nb_rb(sched_pdsch.Qm,
sched_pdsch.R,
1, // no transform precoding for DL
sched_pdsch.nrOfLayers,
tda_info.nrOfSymbols,
sched_pdsch.dmrs_parms.N_PRB_DMRS * sched_pdsch.dmrs_parms.N_DMRS_SLOT,
sched_ctrl->num_total_bytes + oh,
min_rbSize,
max_rbSize,
&sched_pdsch.tb_size,
&sched_pdsch.rbSize);
post_process_dlsch(mac, pp_pdsch, iterator->UE, &sched_pdsch);
/* transmissions: directly allocate */
n_rb_sched[beam.idx] -= sched_pdsch.rbSize;
for (int rb = bwp_start + sched_pdsch.rbStart; rb < bwp_start + sched_pdsch.rbStart + sched_pdsch.rbSize; rb++)
rballoc_mask[rb] |= slbitmap;
remainUEs[beam.idx]--;
iterator++;
} }
} }
static void nr_dlsch_preprocessor(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch) void nr_dlsch_preprocessor(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch)
{ {
NR_UEs_t *UE_info = &mac->UE_info; NR_UEs_t *UE_info = &mac->UE_info;
...@@ -911,13 +883,7 @@ static void nr_dlsch_preprocessor(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pd ...@@ -911,13 +883,7 @@ static void nr_dlsch_preprocessor(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pd
// FAPI cannot handle more than MAX_DCI_CORESET DCIs // FAPI cannot handle more than MAX_DCI_CORESET DCIs
max_sched_ues = min(max_sched_ues, MAX_DCI_CORESET); max_sched_ues = min(max_sched_ues, MAX_DCI_CORESET);
/* proportional fair scheduling algorithm */ nr_dl_schedule(mac, pp_pdsch, UE_info->connected_ue_list, max_sched_ues, num_beams, n_rb_sched);
pf_dl(mac, pp_pdsch, UE_info->connected_ue_list, max_sched_ues, num_beams, n_rb_sched);
}
nr_pp_impl_dl nr_init_dlsch_preprocessor()
{
return nr_dlsch_preprocessor;
} }
nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu_t *dl_tti_pdsch_pdu, nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu_t *dl_tti_pdsch_pdu,
...@@ -998,30 +964,16 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu ...@@ -998,30 +964,16 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu
return pdsch_pdu; return pdsch_pdu;
} }
void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE_info_t *UE, NR_sched_pdsch_t *sched_pdsch) // Resolve HARQ PID, remove from available/retrans list, set up feedback.
// Sets sched_pdsch->dl_harq_pid. Returns the harq process pointer.
static NR_UE_harq_t *setup_dl_harq_process(NR_UE_sched_ctrl_t *sched_ctrl, NR_sched_pdsch_t *sched_pdsch, rnti_t rnti)
{ {
int CC_id = 0;
int module_id = nr_mac->Mod_id;
frame_t frame = pdsch->frame;
slot_t slot = pdsch->slot;
const NR_ServingCellConfigCommon_t *scc = nr_mac->common_channels[CC_id].ServingCellConfigCommon;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *current_BWP = &UE->current_DL_BWP;
const rnti_t rnti = UE->rnti;
/* POST processing */
const uint8_t nrOfLayers = sched_pdsch->nrOfLayers;
const uint32_t TBS = sched_pdsch->tb_size;
int8_t current_harq_pid = sched_pdsch->dl_harq_pid; int8_t current_harq_pid = sched_pdsch->dl_harq_pid;
if (current_harq_pid < 0) { if (current_harq_pid < 0) {
/* PP has not selected a specific HARQ Process, get a new one */ /* PP has not selected a specific HARQ Process, get a new one */
current_harq_pid = sched_ctrl->available_dl_harq.head; current_harq_pid = sched_ctrl->available_dl_harq.head;
AssertFatal(current_harq_pid >= 0, AssertFatal(current_harq_pid >= 0, "no free HARQ process available for UE %04x\n", rnti);
"no free HARQ process available for UE %04x\n",
UE->rnti);
remove_front_nr_list(&sched_ctrl->available_dl_harq); remove_front_nr_list(&sched_ctrl->available_dl_harq);
sched_pdsch->dl_harq_pid = current_harq_pid; sched_pdsch->dl_harq_pid = current_harq_pid;
} else { } else {
...@@ -1034,22 +986,265 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE ...@@ -1034,22 +986,265 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
remove_nr_list(&sched_ctrl->retrans_dl_harq, current_harq_pid); remove_nr_list(&sched_ctrl->retrans_dl_harq, current_harq_pid);
} }
NR_tda_info_t *tda_info = &sched_pdsch->tda_info;
NR_pdsch_dmrs_t *dmrs_parms = &sched_pdsch->dmrs_parms;
NR_UE_harq_t *harq = &sched_ctrl->harq_processes[current_harq_pid]; NR_UE_harq_t *harq = &sched_ctrl->harq_processes[current_harq_pid];
NR_sched_pucch_t *pucch = NULL;
DevAssert(!harq->is_waiting); DevAssert(!harq->is_waiting);
if (sched_pdsch->pucch_allocation >= 0) { if (sched_pdsch->pucch_allocation < 0) {
pucch = &sched_ctrl->sched_pucch[sched_pdsch->pucch_allocation]; finish_nr_dl_harq(sched_ctrl, current_harq_pid);
} else {
NR_sched_pucch_t *pucch = &sched_ctrl->sched_pucch[sched_pdsch->pucch_allocation];
add_tail_nr_list(&sched_ctrl->feedback_dl_harq, current_harq_pid); add_tail_nr_list(&sched_ctrl->feedback_dl_harq, current_harq_pid);
harq->feedback_frame = pucch->frame; harq->feedback_frame = pucch->frame;
harq->feedback_slot = pucch->ul_slot; harq->feedback_slot = pucch->ul_slot;
harq->is_waiting = true; harq->is_waiting = true;
} }
return harq;
}
static void generate_dl_mac_pdu(gNB_MAC_INST *mac,
NR_UE_info_t *UE,
NR_UE_harq_t *harq,
NR_sched_pdsch_t *sched_pdsch,
const nr_dl_candidate_t *candidate,
frame_t frame,
slot_t slot)
{
const int module_id = mac->Mod_id;
const rnti_t rnti = UE->rnti;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
const int8_t current_harq_pid = sched_pdsch->dl_harq_pid;
const uint32_t TBS = sched_pdsch->tb_size;
if (harq->round != 0) { /* retransmission */
/* we do not have to do anything, since we do not require to get data
* from RLC or encode MAC CEs. The TX_req structure is filled below
* or copy data to FAPI structures */
LOG_D(NR_MAC,
"%d.%2d DL retransmission RNTI %04x HARQ PID %d round %d NDI %d\n",
frame,
slot,
rnti,
current_harq_pid,
harq->round,
harq->ndi);
if (!get_softmodem_params()->phy_test)
AssertFatal(harq->sched_pdsch.tb_size == TBS,
"UE %04x mismatch between scheduled TBS and buffered TB for HARQ PID %d\n",
UE->rnti,
current_harq_pid);
else if (harq->sched_pdsch.tb_size != TBS)
LOG_E(NR_MAC,
"Mismatch between scheduled TBS and buffered TB for HARQ PID %d. No RTX control in phy-test mode. "
"Possible causes: presence of CSI-RS or DLSCH scheduled in the mixed slot.\n",
current_harq_pid);
T(T_GNB_MAC_RETRANSMISSION_DL_PDU_WITH_DATA,
T_INT(module_id),
T_INT(0),
T_INT(rnti),
T_INT(frame),
T_INT(slot),
T_INT(current_harq_pid),
T_INT(harq->round),
T_BUFFER(harq->transportBlock.buf, TBS));
UE->mac_stats.dl.total_rbs_retx += sched_pdsch->rbSize;
mac->mac_stats.dl.used_prb_aggregate += sched_pdsch->rbSize;
} else { /* initial transmission */
LOG_D(NR_MAC, "Initial HARQ transmission in %d.%d\n", frame, slot);
// Flag HARQ process to start TCI timer at ACK
harq->start_tci_timer = sched_ctrl->UE_mac_ce_ctrl.tci_state_ind.is_scheduled;
uint8_t *buf = allocate_transportBlock_buffer(&harq->transportBlock, TBS);
/* first, write all CEs that might be there */
int written = nr_write_ce_dlsch_pdu(module_id,
sched_ctrl,
(unsigned char *)buf,
255, // no drx
NULL); // contention res id
buf += written;
uint8_t *bufEnd = buf + TBS - written;
DevAssert(TBS > written);
int dlsch_total_bytes = 0;
/* next, get RLC data */
start_meas(&mac->rlc_data_req);
int sdus = 0;
if (sched_ctrl->num_total_bytes > 0) {
/* ask the LCID allocation policy how many bytes each LC gets */
int lcid_alloc[NR_MAX_NUM_LCID] = {0};
mac->dl_lcid_alloc(mac, candidate, bufEnd - buf, lcid_alloc);
/* loop over all activated logical channels */
for (int i = 0; i < seq_arr_size(&sched_ctrl->lc_config); ++i) {
const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, i);
const int lcid = c->lcid;
if (lcid_alloc[lcid] == 0)
continue;
int lcid_bytes = 0;
int lcid_remaining = lcid_alloc[lcid];
while (bufEnd - buf > sizeof(NR_MAC_SUBHEADER_LONG) + 1 && lcid_remaining > 0) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *)buf;
const rlc_buffer_occupancy_t ndata = min(lcid_remaining, bufEnd - buf - sizeof(NR_MAC_SUBHEADER_LONG));
tbs_size_t len = nr_mac_rlc_data_req(module_id, rnti, true, lcid, ndata, (char *)buf + sizeof(NR_MAC_SUBHEADER_LONG));
LOG_D(NR_MAC,
"%4d.%2d RNTI %04x: %d bytes from %s %d (ndata %d, remaining size %ld)\n",
frame,
slot,
rnti,
len,
lcid < 4 ? "DCCH" : "DTCH",
lcid,
ndata,
bufEnd - buf - sizeof(NR_MAC_SUBHEADER_LONG));
if (len == 0)
break;
T(T_GNB_MAC_LCID_DL,
T_INT(rnti),
T_INT(frame),
T_INT(slot),
T_INT(lcid),
T_INT(len * 8),
T_INT(nr_rlc_tx_list_occupancy(rnti, lcid)));
header->R = 0;
header->F = 1;
header->LCID = lcid;
header->L = htons(len);
buf += len + sizeof(NR_MAC_SUBHEADER_LONG);
dlsch_total_bytes += len;
lcid_bytes += len;
lcid_remaining -= len;
sdus += 1;
}
UE->mac_stats.dl.lc_bytes[lcid] += lcid_bytes;
}
} else if (get_softmodem_params()->phy_test || get_softmodem_params()->do_ra) {
/* we will need the large header, phy-test typically allocates all
* resources and fills to the last byte below */
LOG_D(NR_MAC, "Configuring DL_TX in %d.%d: TBS %d of random data\n", frame, slot, TBS);
if (bufEnd - buf > sizeof(NR_MAC_SUBHEADER_LONG)) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *)buf;
// fill dlsch_buffer with random data
header->R = 0;
header->F = 1;
header->LCID = DL_SCH_LCID_PADDING;
buf += sizeof(NR_MAC_SUBHEADER_LONG);
header->L = htons(bufEnd - buf);
for (; ((intptr_t)buf) % 4; buf++)
*buf = lrand48() & 0xff;
for (; buf < bufEnd - 3; buf += 4) {
uint32_t *buf32 = (uint32_t *)buf;
*buf32 = lrand48();
}
for (; buf < bufEnd; buf++)
*buf = lrand48() & 0xff;
sdus += 1;
}
}
stop_meas(&mac->rlc_data_req);
// Add padding header and zero rest out if there is space left
if (bufEnd - buf > 0) {
NR_MAC_SUBHEADER_FIXED *padding = (NR_MAC_SUBHEADER_FIXED *)buf;
padding->R = 0;
padding->LCID = DL_SCH_LCID_PADDING;
buf += 1;
memset(buf, 0, bufEnd - buf);
buf = bufEnd;
}
UE->mac_stats.dl.total_bytes += TBS;
UE->mac_stats.dl.current_bytes = TBS;
UE->mac_stats.dl.total_rbs += sched_pdsch->rbSize;
UE->mac_stats.dl.num_mac_sdu += sdus;
UE->mac_stats.dl.current_rbs = sched_pdsch->rbSize;
UE->mac_stats.dl.total_sdu_bytes += dlsch_total_bytes;
mac->mac_stats.dl.used_prb_aggregate += sched_pdsch->rbSize;
/* save retransmission information */
harq->sched_pdsch = *sched_pdsch;
/* save which time allocation has been used, to be used on
* retransmissions */
harq->sched_pdsch.time_domain_allocation = sched_pdsch->time_domain_allocation;
// reset TCI state
if (sched_ctrl->UE_mac_ce_ctrl.tci_state_ind.is_scheduled)
sched_ctrl->UE_mac_ce_ctrl.tci_state_ind.is_scheduled = false;
// ta command is sent, values are reset
if (sched_ctrl->ta_apply) {
sched_ctrl->ta_apply = false;
sched_ctrl->ta_update = 31;
sched_ctrl->ta_frame = (frame + 100) % MAX_FRAME_NUMBER;
LOG_D(NR_MAC, "%d.%2d UE %04x TA scheduled, setting next TA frame to %d\n", frame, slot, UE->rnti, sched_ctrl->ta_frame);
}
T(T_GNB_MAC_DL_PDU_WITH_DATA,
T_INT(module_id),
T_INT(0),
T_INT(rnti),
T_INT(frame),
T_INT(slot),
T_INT(current_harq_pid),
T_BUFFER(harq->transportBlock.buf, TBS));
T(T_GNB_MAC_DL, T_INT(rnti), T_INT(frame), T_INT(slot), T_INT(sched_pdsch->mcs), T_INT(TBS));
}
}
static void fill_dl_tx_request(post_process_pdsch_t *pdsch,
const uint8_t *buf,
int pduindex,
uint32_t TBS,
frame_t frame,
slot_t slot)
{
const int ntx_req = pdsch->TX_req->Number_of_PDUs;
nfapi_nr_pdu_t *tx_req = &pdsch->TX_req->pdu_list[ntx_req];
tx_req->PDU_index = pduindex;
tx_req->num_TLV = 1;
tx_req->TLVs[0].length = TBS;
tx_req->PDU_length = compute_PDU_length(tx_req->num_TLV, tx_req->TLVs[0].length);
memcpy(tx_req->TLVs[0].value.direct, buf, TBS);
pdsch->TX_req->Number_of_PDUs++;
pdsch->TX_req->SFN = frame;
pdsch->TX_req->Slot = slot;
}
void post_process_dlsch(gNB_MAC_INST *nr_mac,
post_process_pdsch_t *pdsch,
NR_UE_info_t *UE,
NR_sched_pdsch_t *sched_pdsch,
const nr_dl_candidate_t *candidate)
{
int CC_id = 0;
frame_t frame = pdsch->frame;
slot_t slot = pdsch->slot;
DevAssert(candidate != NULL);
const NR_ServingCellConfigCommon_t *scc = nr_mac->common_channels[CC_id].ServingCellConfigCommon;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *current_BWP = &UE->current_DL_BWP;
const rnti_t rnti = UE->rnti;
/* POST processing */
const uint8_t nrOfLayers = sched_pdsch->nrOfLayers;
const uint32_t TBS = sched_pdsch->tb_size;
NR_UE_harq_t *harq = setup_dl_harq_process(sched_ctrl, sched_pdsch, rnti);
int8_t current_harq_pid = sched_pdsch->dl_harq_pid;
NR_tda_info_t *tda_info = &sched_pdsch->tda_info;
NR_pdsch_dmrs_t *dmrs_parms = &sched_pdsch->dmrs_parms;
NR_sched_pucch_t *pucch = sched_pdsch->pucch_allocation >= 0 ? &sched_ctrl->sched_pucch[sched_pdsch->pucch_allocation] : NULL;
UE->mac_stats.dl.rounds[harq->round]++; UE->mac_stats.dl.rounds[harq->round]++;
int tpc = nr_mac_get_tpc(&sched_ctrl->pucch_pc); int tpc = nr_mac_get_tpc(&sched_ctrl->pucch_pc);
LOG_D(NR_MAC, LOG_D(NR_MAC,
"%4d.%2d [DLSCH/PDSCH/PUCCH] RNTI %04x DCI L %d start %3d RBs %3d startSymbol %2d nb_symbol %2d dmrspos %x MCS %2d nrOfLayers %d TBS %4d HARQ PID %2d round %d RV %d NDI %d dl_data_to_ULACK %d (%d.%d) PUCCH allocation %d TPC %d\n", "%4d.%2d [DLSCH/PDSCH/PUCCH] RNTI %04x DCI L %d start %3d RBs %3d startSymbol %2d nb_symbol %2d dmrspos %x MCS %2d "
"nrOfLayers %d TBS %4d HARQ PID %2d round %d RV %d NDI %d dl_data_to_ULACK %d (%d.%d) PUCCH allocation %d TPC %d\n",
frame, frame,
slot, slot,
rnti, rnti,
...@@ -1084,10 +1279,10 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE ...@@ -1084,10 +1279,10 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
nfapi_nr_dl_tti_request_pdu_t *dl_tti_pdcch_pdu = &pdsch->dl_req->dl_tti_pdu_list[pdsch->dl_req->nPDUs]; nfapi_nr_dl_tti_request_pdu_t *dl_tti_pdcch_pdu = &pdsch->dl_req->dl_tti_pdu_list[pdsch->dl_req->nPDUs];
memset(dl_tti_pdcch_pdu, 0, sizeof(nfapi_nr_dl_tti_request_pdu_t)); memset(dl_tti_pdcch_pdu, 0, sizeof(nfapi_nr_dl_tti_request_pdu_t));
dl_tti_pdcch_pdu->PDUType = NFAPI_NR_DL_TTI_PDCCH_PDU_TYPE; dl_tti_pdcch_pdu->PDUType = NFAPI_NR_DL_TTI_PDCCH_PDU_TYPE;
dl_tti_pdcch_pdu->PDUSize = (uint8_t)(2+sizeof(nfapi_nr_dl_tti_pdcch_pdu)); dl_tti_pdcch_pdu->PDUSize = (uint8_t)(2 + sizeof(nfapi_nr_dl_tti_pdcch_pdu));
pdsch->dl_req->nPDUs += 1; pdsch->dl_req->nPDUs += 1;
pdcch_pdu = &dl_tti_pdcch_pdu->pdcch_pdu.pdcch_pdu_rel15; pdcch_pdu = &dl_tti_pdcch_pdu->pdcch_pdu.pdcch_pdu_rel15;
LOG_D(NR_MAC,"Trying to configure DL pdcch for UE %04x, bwp %d, cs %d\n", UE->rnti, bwp_id, coresetid); LOG_D(NR_MAC, "Trying to configure DL pdcch for UE %04x, bwp %d, cs %d\n", UE->rnti, bwp_id, coresetid);
NR_ControlResourceSet_t *coreset = sched_ctrl->coreset; NR_ControlResourceSet_t *coreset = sched_ctrl->coreset;
nr_configure_pdcch(pdcch_pdu, coreset, &sched_ctrl->sched_pdcch); nr_configure_pdcch(pdcch_pdu, coreset, &sched_ctrl->sched_pdcch);
nr_mac->pdcch_pdu_idx[CC_id][coresetid] = pdcch_pdu; nr_mac->pdcch_pdu_idx[CC_id][coresetid] = pdcch_pdu;
...@@ -1126,8 +1321,7 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE ...@@ -1126,8 +1321,7 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
nl_tbslbrm, nl_tbslbrm,
pduindex); pduindex);
LOG_D(NR_MAC, "Configuring DCI/PDCCH in %d.%d at CCE %d, rnti %x\n", frame, slot, sched_ctrl->cce_index, rnti);
LOG_D(NR_MAC,"Configuring DCI/PDCCH in %d.%d at CCE %d, rnti %x\n", frame,slot,sched_ctrl->cce_index,rnti);
/* Fill PDCCH DL DCI PDU */ /* Fill PDCCH DL DCI PDU */
nfapi_nr_dl_dci_pdu_t *dci_pdu = prepare_dci_pdu(pdcch_pdu, nfapi_nr_dl_dci_pdu_t *dci_pdu = prepare_dci_pdu(pdcch_pdu,
scc, scc,
...@@ -1154,9 +1348,9 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE ...@@ -1154,9 +1348,9 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
false); false);
NR_PDSCH_Config_t *pdsch_Config = current_BWP->pdsch_Config; NR_PDSCH_Config_t *pdsch_Config = current_BWP->pdsch_Config;
AssertFatal(pdsch_Config == NULL AssertFatal(
|| pdsch_Config->resourceAllocation == NR_PDSCH_Config__resourceAllocation_resourceAllocationType1, pdsch_Config == NULL || pdsch_Config->resourceAllocation == NR_PDSCH_Config__resourceAllocation_resourceAllocationType1,
"Only frequency resource allocation type 1 is currently supported\n"); "Only frequency resource allocation type 1 is currently supported\n");
LOG_D(NR_MAC, LOG_D(NR_MAC,
"%4d.%2d DCI type 1 payload: freq_alloc %d (%d,%d,%d), " "%4d.%2d DCI type 1 payload: freq_alloc %d (%d,%d,%d), "
...@@ -1195,173 +1389,8 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE ...@@ -1195,173 +1389,8 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
pdcch_pdu->StartSymbolIndex, pdcch_pdu->StartSymbolIndex,
pdcch_pdu->DurationSymbols); pdcch_pdu->DurationSymbols);
if (harq->round != 0) { /* retransmission */ generate_dl_mac_pdu(nr_mac, UE, harq, sched_pdsch, candidate, frame, slot);
/* we do not have to do anything, since we do not require to get data fill_dl_tx_request(pdsch, harq->transportBlock.buf, pduindex, TBS, frame, slot);
* from RLC or encode MAC CEs. The TX_req structure is filled below
* or copy data to FAPI structures */
LOG_D(NR_MAC,
"%d.%2d DL retransmission RNTI %04x HARQ PID %d round %d NDI %d\n",
frame,
slot,
rnti,
current_harq_pid,
harq->round,
harq->ndi);
if (!get_softmodem_params()->phy_test)
AssertFatal(harq->sched_pdsch.tb_size == TBS,
"UE %04x mismatch between scheduled TBS and buffered TB for HARQ PID %d\n",
UE->rnti,
current_harq_pid);
else if (harq->sched_pdsch.tb_size != TBS)
LOG_E(NR_MAC,
"Mismatch between scheduled TBS and buffered TB for HARQ PID %d. No RTX control in phy-test mode. "
"Possible causes: presence of CSI-RS or DLSCH scheduled in the mixed slot.\n",
current_harq_pid);
T(T_GNB_MAC_RETRANSMISSION_DL_PDU_WITH_DATA, T_INT(module_id), T_INT(CC_id), T_INT(rnti),
T_INT(frame), T_INT(slot), T_INT(current_harq_pid), T_INT(harq->round), T_BUFFER(harq->transportBlock.buf, TBS));
UE->mac_stats.dl.total_rbs_retx += sched_pdsch->rbSize;
nr_mac->mac_stats.dl.used_prb_aggregate += sched_pdsch->rbSize;
} else { /* initial transmission */
LOG_D(NR_MAC, "Initial HARQ transmission in %d.%d\n", frame, slot);
// Flag HARQ process to start TCI timer at ACK
harq->start_tci_timer = sched_ctrl->UE_mac_ce_ctrl.tci_state_ind.is_scheduled;
uint8_t *buf = allocate_transportBlock_buffer(&harq->transportBlock, TBS);
/* first, write all CEs that might be there */
int written = nr_write_ce_dlsch_pdu(module_id,
sched_ctrl,
(unsigned char *)buf,
255, // no drx
NULL); // contention res id
buf += written;
uint8_t *bufEnd = buf + TBS - written;
DevAssert(TBS > written);
int dlsch_total_bytes = 0;
/* next, get RLC data */
start_meas(&nr_mac->rlc_data_req);
int sdus = 0;
if (sched_ctrl->num_total_bytes > 0) {
/* loop over all activated logical channels */
for (int i = 0; i < seq_arr_size(&sched_ctrl->lc_config); ++i) {
const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, i);
const int lcid = c->lcid;
if (sched_ctrl->rlc_status[lcid].bytes_in_buffer == 0)
continue; // no data for this LC tbs_size_t len = 0;
tb_size_t pdu_siz[MAX_NUM_DATA_REQ];
int num = nr_mac_rlc_multi_data_req(module_id, rnti, true, lcid, bufEnd - buf, (char *)buf, pdu_siz, sizeofArray(pdu_siz));
DevAssert(num <= sizeofArray(pdu_siz));
sdus += num;
int lcid_bytes = 0;
for (int j = 0; j < num; ++j) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *) buf;
header->R = 0;
header->F = 1;
header->LCID = lcid;
header->L = htons(pdu_siz[j]);
buf += pdu_siz[j] + sizeof(NR_MAC_SUBHEADER_LONG);
dlsch_total_bytes += pdu_siz[j];
lcid_bytes += pdu_siz[j];
}
T(T_GNB_MAC_LCID_DL, T_INT(rnti), T_INT(frame), T_INT(slot), T_INT(lcid), T_INT(lcid_bytes), T_INT(nr_rlc_tx_list_occupancy(rnti, lcid)));
UE->mac_stats.dl.lc_bytes[lcid] += lcid_bytes;
}
} else if (get_softmodem_params()->phy_test || get_softmodem_params()->do_ra) {
/* we will need the large header, phy-test typically allocates all
* resources and fills to the last byte below */
LOG_D(NR_MAC, "Configuring DL_TX in %d.%d: TBS %d of random data\n", frame, slot, TBS);
if (bufEnd-buf > sizeof(NR_MAC_SUBHEADER_LONG) ) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *) buf;
// fill dlsch_buffer with random data
header->R = 0;
header->F = 1;
header->LCID = DL_SCH_LCID_PADDING;
buf += sizeof(NR_MAC_SUBHEADER_LONG);
header->L = htons(bufEnd-buf);
for (; ((intptr_t)buf) % 4; buf++)
*buf = lrand48() & 0xff;
for (; buf < bufEnd - 3; buf += 4) {
uint32_t *buf32 = (uint32_t *)buf;
*buf32 = lrand48();
}
for (; buf < bufEnd; buf++)
*buf = lrand48() & 0xff;
sdus +=1;
}
}
stop_meas(&nr_mac->rlc_data_req);
// Add padding header and zero rest out if there is space left
if (bufEnd-buf > 0) {
NR_MAC_SUBHEADER_FIXED *padding = (NR_MAC_SUBHEADER_FIXED *) buf;
padding->R = 0;
padding->LCID = DL_SCH_LCID_PADDING;
buf += 1;
memset(buf,0,bufEnd-buf);
buf=bufEnd;
}
UE->mac_stats.dl.total_bytes += TBS;
UE->mac_stats.dl.current_bytes = TBS;
UE->mac_stats.dl.total_rbs += sched_pdsch->rbSize;
UE->mac_stats.dl.num_mac_sdu += sdus;
UE->mac_stats.dl.current_rbs = sched_pdsch->rbSize;
UE->mac_stats.dl.total_sdu_bytes += dlsch_total_bytes;
nr_mac->mac_stats.dl.used_prb_aggregate += sched_pdsch->rbSize;
/* save retransmission information */
harq->sched_pdsch = *sched_pdsch;
/* save which time allocation has been used, to be used on
* retransmissions */
harq->sched_pdsch.time_domain_allocation = sched_pdsch->time_domain_allocation;
// reset TCI state
if (sched_ctrl->UE_mac_ce_ctrl.tci_state_ind.is_scheduled)
sched_ctrl->UE_mac_ce_ctrl.tci_state_ind.is_scheduled = false;
// ta command is sent, values are reset
if (sched_ctrl->ta_apply) {
sched_ctrl->ta_apply = false;
sched_ctrl->ta_update = 31;
sched_ctrl->ta_frame = (frame + 100) % MAX_FRAME_NUMBER;
LOG_D(NR_MAC, "%d.%2d UE %04x TA scheduled, setting next TA frame to %d\n", frame, slot, UE->rnti, sched_ctrl->ta_frame);
}
T(T_GNB_MAC_DL_PDU_WITH_DATA, T_INT(module_id), T_INT(CC_id), T_INT(rnti),
T_INT(frame), T_INT(slot), T_INT(current_harq_pid), T_BUFFER(harq->transportBlock.buf, TBS));
T(T_GNB_MAC_DL, T_INT(rnti), T_INT(frame), T_INT(slot), T_INT(sched_pdsch->mcs), T_INT(TBS));
}
DevAssert(nrOfLayers >= 1 && nrOfLayers <= 8);
DevAssert(current_BWP->mcsTableIdx >= 0 && current_BWP->mcsTableIdx <= 1);
DevAssert(sched_pdsch->mcs >= 0 && sched_pdsch->mcs <= 31);
NR_du_stats_t *stats = &nr_mac->du_stats;
stats->pdsch_mcs_dist[nrOfLayers - 1][current_BWP->mcsTableIdx][sched_pdsch->mcs] += sched_pdsch->rbSize;
const int ntx_req = pdsch->TX_req->Number_of_PDUs;
nfapi_nr_pdu_t *tx_req = &pdsch->TX_req->pdu_list[ntx_req];
tx_req->PDU_index = pduindex;
tx_req->num_TLV = 1;
tx_req->TLVs[0].length = TBS;
tx_req->PDU_length = compute_PDU_length(tx_req->num_TLV, tx_req->TLVs[0].length);
memcpy(tx_req->TLVs[0].value.direct, harq->transportBlock.buf, TBS);
pdsch->TX_req->Number_of_PDUs++;
pdsch->TX_req->SFN = frame;
pdsch->TX_req->Slot = slot;
// RESET HARQ NDI and ROUND once they are used.
// as HARQ is disabled and there will no PUCCH being received.
if (sched_pdsch->pucch_allocation < 0) {
finish_nr_dl_harq(sched_ctrl, current_harq_pid);
}
} }
void nr_schedule_ue_spec(module_id_t module_id, void nr_schedule_ue_spec(module_id_t module_id,
......
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
/*!
* \brief Default pluggable policy functions for the DL scheduler pipeline.
*
* These are the built-in implementations behind the function pointers
* (dl_ri_pmi_select, dl_tda_select, dl_beam_select, dl_mcs_select, dl_rb_alloc, dl_lcid_alloc)
* wired up at MAC init time. They can be replaced at runtime by external
* scheduler plug-ins without touching the core scheduling loop in
* gNB_scheduler_dlsch.c.
*/
#include "common/utils/nr/nr_common.h"
#include "gNB_scheduler_dlsch_default_policies.h"
/*MAC*/
#include "NR_MAC_COMMON/nr_mac.h"
#include "NR_MAC_gNB/nr_mac_gNB.h"
#include "LAYER2/NR_MAC_gNB/mac_proto.h"
#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
/*TAG*/
#include "NR_TAG-Id.h"
/*Softmodem params*/
#include "executables/softmodem-common.h"
#include "../../../nfapi/oai_integration/vendor_ext.h"
// Default RI/PMI selector: reads rank and PMI from CSI feedback for new-tx,
// or from HARQ process state for retx.
void nr_dl_ri_pmi_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates)
{
FOR_EACH_CANDIDATE(cand, candidates, n_candidates)
{
NR_UE_sched_ctrl_t *sched_ctrl = &cand->UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *dl_bwp = &cand->UE->current_DL_BWP;
if (cand->is_retx) {
cand->sched_pdsch.nrOfLayers = sched_ctrl->harq_processes[cand->retx_harq_pid].sched_pdsch.nrOfLayers;
cand->sched_pdsch.pm_index =
get_pm_index(mac, cand->UE, dl_bwp->dci_format, cand->sched_pdsch.nrOfLayers, mac->radio_config.pdsch_AntennaPorts.XP);
} else {
cand->sched_pdsch.nrOfLayers = cand->csi_ri + 1;
cand->sched_pdsch.pm_index = cand->csi_pm_index;
}
}
}
// Default TDA selector: picks the slot-wide TDA index from get_dl_tda(),
// then resolves tda_info per candidate using each UE's own BWP / search
// space / coreset. Marks invalids with skipped=true.
int nr_dl_tda_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates, frame_t frame, slot_t slot)
{
int tda = get_dl_tda(mac, slot);
AssertFatal(tda >= 0, "Unable to find PDSCH time domain allocation in list\n");
const NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon;
int n_valid = 0;
FOR_EACH_CANDIDATE(cand, candidates, n_candidates)
{
if (cand->skipped)
continue;
NR_UE_info_t *UE = cand->UE;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
int coresetid = sched_ctrl->coreset->controlResourceSetId;
NR_tda_info_t tda_info = get_dl_tda_info(dl_bwp,
sched_ctrl->search_space->searchSpaceType->present,
tda,
scc->dmrs_TypeA_Position,
1,
TYPE_C_RNTI_,
coresetid,
false);
if (!tda_info.valid_tda) {
cand->skipped = true;
continue;
}
/* For retransmissions with a changed TDA, refit rbSize to preserve TBS */
if (cand->is_retx) {
const NR_sched_pdsch_t *orig = &sched_ctrl->harq_processes[cand->retx_harq_pid].sched_pdsch;
bool tda_changed =
tda_info.startSymbolIndex != orig->tda_info.startSymbolIndex || tda_info.nrOfSymbols != orig->tda_info.nrOfSymbols;
if (tda_changed) {
uint16_t new_rbSize = check_dl_retx_feasibility(cand, tda, &tda_info, scc, dl_bwp->BWPSize);
if (!new_rbSize) {
cand->skipped = true;
continue;
}
cand->retx_rbSize = new_rbSize;
}
}
cand->sched_pdsch.time_domain_allocation = tda;
cand->sched_pdsch.tda_info = tda_info;
cand->alloc_slbitmap = SL_to_bitmap(tda_info.startSymbolIndex, tda_info.nrOfSymbols);
n_valid++;
}
return n_valid;
}
static int compare_dl_pf_ptrs(const void *a, const void *b)
{
const nr_dl_candidate_t *ca = *(const nr_dl_candidate_t *const *)a;
const nr_dl_candidate_t *cb = *(const nr_dl_candidate_t *const *)b;
/* retx first (INFINITY weight), then highest PF weight */
float wa = ca->is_retx ? INFINITY : dl_pf_weight(ca->current_mcs, ca->mcs_table, ca->sched_pdsch.nrOfLayers, ca->avg_throughput);
float wb = cb->is_retx ? INFINITY : dl_pf_weight(cb->current_mcs, cb->mcs_table, cb->sched_pdsch.nrOfLayers, cb->avg_throughput);
return (wa < wb) - (wa > wb);
}
int nr_dl_beam_select_default(NR_beam_info_t *beam_info,
const int16_t *beam_index_list,
nr_dl_candidate_t *candidates,
int n_candidates,
frame_t frame,
slot_t slot,
int slots_per_frame)
{
/* Build pointer array sorted by PF priority so retx and high-priority UEs claim beams first. */
nr_dl_candidate_t *order[MAX_MOBILES_PER_GNB];
int n_active = 0;
FOR_EACH_CANDIDATE(cand, candidates, n_candidates)
if (!cand->skipped)
order[n_active++] = cand;
qsort(order, n_active, sizeof(*order), compare_dl_pf_ptrs);
int n_valid = 0;
for (int i = 0; i < n_active; i++) {
nr_dl_candidate_t *cand = order[i];
NR_beam_alloc_t beam = beam_allocation_procedure(beam_info, frame, slot, cand->alloc_beam_dir, slots_per_frame);
if (beam.idx < 0) {
cand->skipped = true;
continue;
}
cand->alloc_beam_idx = beam.idx;
cand->alloc_new_beam = beam.new_beam;
n_valid++;
}
return n_valid;
}
void nr_dl_mcs_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates)
{
const NR_bler_options_t *bo = &mac->dl_bler;
FOR_EACH_CANDIDATE(cand, candidates, n_candidates)
{
int mcs;
if (cand->is_retx) {
mcs = cand->current_mcs; /* retx MCS is fixed by the HARQ round */
} else if (bo->harq_round_max == 1) {
mcs = max(bo->min_mcs, min(bo->max_mcs, cand->max_mcs));
} else if (!cand->bler_updated) {
mcs = cand->current_mcs;
} else {
mcs = nr_adapt_mcs_from_bler(cand->current_mcs,
bo->min_mcs,
cand->max_mcs,
cand->bler,
bo->lower,
bo->upper,
cand->last_num_sched);
}
cand->sched_pdsch.mcs = mcs;
/* Persist for all candidates — BLER-based MCS ramps even for UEs the
* policy rejects this slot (failed CCE, no free RBs, etc.). */
if (!cand->is_retx)
cand->UE->UE_sched_ctrl.dl_bler_stats.mcs = mcs;
}
}
static int compare_dl_pf_rb_ptrs(const void *a, const void *b)
{
const nr_dl_candidate_t *ca = *(const nr_dl_candidate_t *const *)a;
const nr_dl_candidate_t *cb = *(const nr_dl_candidate_t *const *)b;
/* retx first, then highest PF weight (uses sched_pdsch.mcs, which is set by mcs_select) */
float wa =
ca->is_retx ? INFINITY : dl_pf_weight(ca->sched_pdsch.mcs, ca->mcs_table, ca->sched_pdsch.nrOfLayers, ca->avg_throughput);
float wb =
cb->is_retx ? INFINITY : dl_pf_weight(cb->sched_pdsch.mcs, cb->mcs_table, cb->sched_pdsch.nrOfLayers, cb->avg_throughput);
return (wa < wb) - (wa > wb);
}
int nr_dl_proportional_fair(const nr_dl_sched_params_t *params, nr_dl_candidate_t *candidates, int n_candidates)
{
const int min_rbSize = 5;
int n_scheduled = 0;
/* Build pointer array sorted by PF priority (retx first, then highest weight) */
nr_dl_candidate_t *order[MAX_MOBILES_PER_GNB];
int n_active = 0;
FOR_EACH_CANDIDATE(cand, candidates, n_candidates)
if (!cand->skipped)
order[n_active++] = cand;
qsort(order, n_active, sizeof(*order), compare_dl_pf_rb_ptrs);
/* Phase 1: HARQ retransmissions (highest priority, exact RBs) */
for (int j = 0; j < n_active; j++) {
nr_dl_candidate_t *cand = order[j];
if (!cand->is_retx)
continue;
int needed_rbs = cand->retx_rbSize;
uint16_t *vrb_map = params->vrb_map[cand->alloc_beam_idx];
int rbStart, rbSize;
if (!get_rb_alloc(needed_rbs,
cand->bwp_size,
cand->bwp_start,
cand->bwp_size,
vrb_map,
cand->alloc_slbitmap,
&rbStart,
&rbSize))
continue;
COMMIT_ALLOC(params, cand, rbStart, needed_rbs, cand->sched_pdsch.mcs, n_scheduled);
}
/* Phase 2: No-data UEs (TA command or beam switch MAC CE, no RLC data) */
for (int j = 0; j < n_active; j++) {
nr_dl_candidate_t *cand = order[j];
if (cand->is_retx || cand->pending_bytes > 0)
continue;
uint16_t *vrb_map = params->vrb_map[cand->alloc_beam_idx];
int rbStart, rbSize;
if (!get_rb_alloc(min_rbSize,
cand->bwp_size,
cand->bwp_start,
cand->bwp_size,
vrb_map,
cand->alloc_slbitmap,
&rbStart,
&rbSize))
continue;
COMMIT_ALLOC(params, cand, rbStart, min_rbSize, cand->sched_pdsch.mcs, n_scheduled);
}
/* Phase 3: New data UEs — PF priority order, largest free block */
for (int j = 0; j < n_active; j++) {
nr_dl_candidate_t *cand = order[j];
if (cand->is_retx || cand->pending_bytes == 0)
continue;
int rbStart;
uint16_t *vrb_map = params->vrb_map[cand->alloc_beam_idx];
int max_rbSize = find_largest_free_block(vrb_map, cand->alloc_slbitmap, cand->bwp_start, cand->bwp_size, &rbStart);
if (max_rbSize < min_rbSize)
continue;
int mcs = cand->sched_pdsch.mcs;
uint8_t Qm = nr_get_Qm_dl(mcs, cand->mcs_table);
uint16_t R = nr_get_code_rate_dl(mcs, cand->mcs_table);
NR_pdsch_dmrs_t dmrs = get_dl_dmrs_params(params->mac->common_channels->ServingCellConfigCommon,
&cand->UE->current_DL_BWP,
&cand->sched_pdsch.tda_info,
cand->sched_pdsch.nrOfLayers);
const int oh = 3 * 4 + (cand->UE->UE_sched_ctrl.ta_apply ? 2 : 0);
uint32_t tbs;
uint16_t rbSize;
nr_find_nb_rb(Qm,
R,
1,
cand->sched_pdsch.nrOfLayers,
cand->sched_pdsch.tda_info.nrOfSymbols,
dmrs.N_PRB_DMRS * dmrs.N_DMRS_SLOT,
cand->pending_bytes + oh,
min_rbSize,
max_rbSize,
&tbs,
&rbSize);
COMMIT_ALLOC(params, cand, rbStart, rbSize, mcs, n_scheduled);
}
return n_scheduled;
}
void nr_dl_lcid_alloc_default(const gNB_MAC_INST *mac,
const nr_dl_candidate_t *candidate,
int tbs_available,
int lcid_alloc[NR_MAX_NUM_LCID])
{
(void)mac;
(void)tbs_available;
memset(lcid_alloc, 0, NR_MAX_NUM_LCID * sizeof(int));
for (int lcid = 0; lcid < NR_MAX_NUM_LCID; lcid++)
lcid_alloc[lcid] = candidate->pending_bytes_per_lcid[lcid];
}
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef GNB_SCHEDULER_DLSCH_DEFAULT_POLICIES_H
#define GNB_SCHEDULER_DLSCH_DEFAULT_POLICIES_H
#include "LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
void nr_dl_ri_pmi_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates);
void nr_dl_mcs_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates);
int nr_dl_beam_select_default(NR_beam_info_t *beam_info,
const int16_t *beam_index_list,
nr_dl_candidate_t *candidates,
int n_candidates,
frame_t frame,
slot_t slot,
int slots_per_frame);
int nr_dl_tda_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates, frame_t frame, slot_t slot);
int nr_dl_proportional_fair(const nr_dl_sched_params_t *params, nr_dl_candidate_t *candidates, int n_candidates);
void nr_dl_lcid_alloc_default(const gNB_MAC_INST *mac,
const nr_dl_candidate_t *candidate,
int tbs_available,
int lcid_alloc[NR_MAX_NUM_LCID]);
#endif /* GNB_SCHEDULER_DLSCH_DEFAULT_POLICIES_H */
...@@ -50,6 +50,8 @@ void nr_preprocessor_phytest(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch) ...@@ -50,6 +50,8 @@ void nr_preprocessor_phytest(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch)
if (!is_xlsch_in_slot(dlsch_slot_bitmap, dlsch_slot_modval, slot_period)) if (!is_xlsch_in_slot(dlsch_slot_bitmap, dlsch_slot_modval, slot_period))
return; return;
NR_UE_info_t *UE = mac->UE_info.connected_ue_list[0]; NR_UE_info_t *UE = mac->UE_info.connected_ue_list[0];
if (UE == NULL)
return;
NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon; NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP; NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
...@@ -176,7 +178,19 @@ void nr_preprocessor_phytest(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch) ...@@ -176,7 +178,19 @@ void nr_preprocessor_phytest(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch)
target_dl_Nl) target_dl_Nl)
>> 3; >> 3;
post_process_dlsch(mac, pp_pdsch, UE, &sched_pdsch); nr_dl_candidate_t candidate = {
.UE = UE,
.rnti = rnti,
.is_retx = sched_pdsch.dl_harq_pid >= 0,
.retx_harq_pid = sched_pdsch.dl_harq_pid,
.pending_bytes = sched_ctrl->num_total_bytes,
.mcs_table = dl_bwp->mcsTableIdx,
.bwp_start = sched_pdsch.bwp_info.bwpStart,
.bwp_size = sched_pdsch.bwp_info.bwpSize,
};
candidate.pending_bytes_per_lcid[lcid] = sched_ctrl->rlc_status[lcid].bytes_in_buffer;
post_process_dlsch(mac, pp_pdsch, UE, &sched_pdsch, &candidate);
/* mark the corresponding RBs as used */ /* mark the corresponding RBs as used */
for (int rb = 0; rb < sched_pdsch.rbSize; rb++) for (int rb = 0; rb < sched_pdsch.rbSize; rb++)
......
...@@ -702,6 +702,34 @@ bool nr_find_nb_rb(uint16_t Qm, ...@@ -702,6 +702,34 @@ bool nr_find_nb_rb(uint16_t Qm,
return *tbs >= bytes && *nb_rb <= nb_rb_max; return *tbs >= bytes && *nb_rb <= nb_rb_max;
} }
// Find the largest contiguous block of free RBs in the VRB map.
// Returns the block size, or 0 if no free RB is found. out_start is set to the
// first RB of the largest block when the returned size is nonzero.
int find_largest_free_block(const uint16_t *vrb_map, uint16_t slbitmap, int bwp_start, int bwp_size, int *out_start)
{
int best_start = 0, best_len = 0;
int cur_start = 0, cur_len = 0;
for (int rb = 0; rb < bwp_size; rb++) {
if (!(vrb_map[rb + bwp_start] & slbitmap)) {
if (cur_len == 0)
cur_start = rb;
cur_len++;
} else {
if (cur_len > best_len) {
best_start = cur_start;
best_len = cur_len;
}
cur_len = 0;
}
}
if (cur_len > best_len) {
best_start = cur_start;
best_len = cur_len;
}
*out_start = best_start;
return best_len;
}
bool get_rb_alloc(int rbSize_min, bool get_rb_alloc(int rbSize_min,
int rbSize_max, int rbSize_max,
int bwpStart, int bwpStart,
...@@ -811,41 +839,44 @@ NR_pusch_dmrs_t get_ul_dmrs_params(const NR_ServingCellConfigCommon_t *scc, ...@@ -811,41 +839,44 @@ NR_pusch_dmrs_t get_ul_dmrs_params(const NR_ServingCellConfigCommon_t *scc,
#define BLER_UPDATE_FRAME 10 #define BLER_UPDATE_FRAME 10
#define BLER_FILTER 0.9f #define BLER_FILTER 0.9f
int get_mcs_from_bler(const NR_bler_options_t *bler_options, int nr_adapt_mcs_from_bler(int current_mcs, int min_mcs, int max_mcs, float bler, float bler_lower, float bler_upper, int num_sched)
const NR_mac_dir_stats_t *stats, {
NR_bler_stats_t *bler_stats, int mcs = current_mcs;
int max_mcs, if (bler < bler_lower && mcs < max_mcs && num_sched > 3)
frame_t frame) mcs++;
else if (bler > bler_upper || num_sched <= 3) // above threshold or no activity
mcs--;
return max(min_mcs, min(mcs, max_mcs));
}
bool update_bler_stats(const NR_bler_options_t *bler_options,
const NR_mac_dir_stats_t *stats,
NR_bler_stats_t *bler_stats,
frame_t frame)
{ {
int diff = frame - bler_stats->last_frame; int diff = frame - bler_stats->last_frame;
if (diff < 0) // wrap around if (diff < 0) // wrap around
diff += 1024; diff += 1024;
max_mcs = min(max_mcs, bler_options->max_mcs);
const uint8_t old_mcs = min(bler_stats->mcs, max_mcs);
if (diff < BLER_UPDATE_FRAME) if (diff < BLER_UPDATE_FRAME)
return old_mcs; // no update return false;
// last update is longer than x frames ago
const int num_dl_sched = (int)(stats->rounds[0] - bler_stats->rounds[0]); const int num_dl_sched = (int)(stats->rounds[0] - bler_stats->rounds[0]);
const int num_dl_retx = (int)(stats->rounds[1] - bler_stats->rounds[1]); const int num_dl_retx = (int)(stats->rounds[1] - bler_stats->rounds[1]);
const float bler_window = num_dl_sched > 0 ? (float) num_dl_retx / num_dl_sched : bler_stats->bler; const float bler_window = num_dl_sched > 0 ? (float)num_dl_retx / num_dl_sched : bler_stats->bler;
bler_stats->bler = BLER_FILTER * bler_stats->bler + (1 - BLER_FILTER) * bler_window; bler_stats->bler = BLER_FILTER * bler_stats->bler + (1 - BLER_FILTER) * bler_window;
int new_mcs = old_mcs;
if (bler_stats->bler < bler_options->lower && old_mcs < max_mcs && num_dl_sched > 3)
new_mcs += 1;
else if (bler_stats->bler > bler_options->upper || num_dl_sched <= 3) // above threshold or no activity
new_mcs -= 1;
// else we are within threshold boundaries
new_mcs = max(new_mcs, bler_options->min_mcs);
bler_stats->last_frame = frame; bler_stats->last_frame = frame;
bler_stats->mcs = new_mcs; bler_stats->last_num_sched = num_dl_sched;
memcpy(bler_stats->rounds, stats->rounds, sizeof(stats->rounds)); memcpy(bler_stats->rounds, stats->rounds, sizeof(stats->rounds));
LOG_D(MAC, "frame %4d MCS %d -> %d (num_dl_sched %d, num_dl_retx %d, BLER wnd %.3f avg %.6f)\n", LOG_D(MAC,
frame, old_mcs, new_mcs, num_dl_sched, num_dl_retx, bler_window, bler_stats->bler); "frame %4d BLER update (num_sched %d, num_retx %d, BLER wnd %.3f avg %.6f)\n",
return new_mcs; frame,
num_dl_sched,
num_dl_retx,
bler_window,
bler_stats->bler);
return true;
} }
nfapi_nr_dl_dci_pdu_t *prepare_dci_pdu(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu, nfapi_nr_dl_dci_pdu_t *prepare_dci_pdu(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu,
...@@ -2989,6 +3020,8 @@ static void init_bler_stats(const NR_bler_options_t *bler_options, NR_bler_stats ...@@ -2989,6 +3020,8 @@ static void init_bler_stats(const NR_bler_options_t *bler_options, NR_bler_stats
NR_UE_info_t *get_new_nr_ue_inst(uid_allocator_t *uia, rnti_t rnti, NR_CellGroupConfig_t *CellGroup, const nr_mac_config_t *config) NR_UE_info_t *get_new_nr_ue_inst(uid_allocator_t *uia, rnti_t rnti, NR_CellGroupConfig_t *CellGroup, const nr_mac_config_t *config)
{ {
NR_UE_info_t *UE = calloc_or_fail(1, sizeof(NR_UE_info_t)); NR_UE_info_t *UE = calloc_or_fail(1, sizeof(NR_UE_info_t));
for (int i = 0; i < MAX_NUM_OF_SSB; i++)
UE->beam_rsrp[i] = UE->beam_sinr[i] = INT16_MIN;
UE->uid = uid_linear_allocator_new(uia); UE->uid = uid_linear_allocator_new(uia);
UE->rnti = rnti; UE->rnti = rnti;
UE->CellGroup = CellGroup; UE->CellGroup = CellGroup;
......
...@@ -501,6 +501,10 @@ static void evaluate_sinr_report(NR_UE_info_t *UE, ...@@ -501,6 +501,10 @@ static void evaluate_sinr_report(NR_UE_info_t *UE,
sched_ctrl->dl_max_mcs = get_mcs_from_SINRx10(mcs_table, sinr_report->r[0].SINRx10, nrOfLayers); sched_ctrl->dl_max_mcs = get_mcs_from_SINRx10(mcs_table, sinr_report->r[0].SINRx10, nrOfLayers);
LOG_D(MAC, "Reported SSB-SINR = %01f, dl_max_mcs %d\n", sinr_report->r[0].SINRx10 / 10.0, sched_ctrl->dl_max_mcs); LOG_D(MAC, "Reported SSB-SINR = %01f, dl_max_mcs %d\n", sinr_report->r[0].SINRx10 / 10.0, sched_ctrl->dl_max_mcs);
for (RSRP_report_t *r = sinr_report->r; r < sinr_report->r + sinr_report->nb; r++)
if (r->resource_id < MAX_NUM_OF_SSB)
UE->beam_sinr[r->resource_id] = r->SINRx10;
} }
static void evaluate_rsrp_report(NR_UE_info_t *UE, static void evaluate_rsrp_report(NR_UE_info_t *UE,
...@@ -574,6 +578,10 @@ static void evaluate_rsrp_report(NR_UE_info_t *UE, ...@@ -574,6 +578,10 @@ static void evaluate_rsrp_report(NR_UE_info_t *UE,
// including ssb rsrp in mac stats // including ssb rsrp in mac stats
stats->cumul_rsrp += rsrp_report->r[0].RSRP; stats->cumul_rsrp += rsrp_report->r[0].RSRP;
stats->num_rsrp_meas++; stats->num_rsrp_meas++;
for (RSRP_report_t *r = rsrp_report->r; r < rsrp_report->r + rsrp_report->nb; r++)
if (r->resource_id < MAX_NUM_OF_SSB)
UE->beam_rsrp[r->resource_id] = r->RSRP;
} }
static void evaluate_cri_report(uint8_t *payload, uint8_t cri_bitlen, int cumul_bits, NR_UE_sched_ctrl_t *sched_ctrl) static void evaluate_cri_report(uint8_t *payload, uint8_t cri_bitlen, int cumul_bits, NR_UE_sched_ctrl_t *sched_ctrl)
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#define __LAYER2_NR_MAC_PROTO_H__ #define __LAYER2_NR_MAC_PROTO_H__
#include "LAYER2/NR_MAC_gNB/nr_mac_gNB.h" #include "LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
#include "LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch_default_policies.h"
#include "NR_TAG-Id.h" #include "NR_TAG-Id.h"
#include "common/ngran_types.h" #include "common/ngran_types.h"
#include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h" #include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h"
...@@ -60,8 +61,8 @@ void nr_schedule_ue_spec(module_id_t module_id, ...@@ -60,8 +61,8 @@ void nr_schedule_ue_spec(module_id_t module_id,
nfapi_nr_dl_tti_request_t *DL_req, nfapi_nr_dl_tti_request_t *DL_req,
nfapi_nr_tx_data_request_t *TX_req); nfapi_nr_tx_data_request_t *TX_req);
/* \brief default DL preprocessor init routine, returns preprocessor to call */ /* \brief default DL preprocessor */
nr_pp_impl_dl nr_init_dlsch_preprocessor(); void nr_dlsch_preprocessor(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch);
void schedule_nr_sib1(module_id_t module_idP, void schedule_nr_sib1(module_id_t module_idP,
frame_t frameP, frame_t frameP,
...@@ -425,6 +426,8 @@ bool nr_find_nb_rb(uint16_t Qm, ...@@ -425,6 +426,8 @@ bool nr_find_nb_rb(uint16_t Qm,
* \param rbSize_ptr Pointer returning the size of the found free block of RBs * \param rbSize_ptr Pointer returning the size of the found free block of RBs
* \return Indicates if a free block of RBs of the required size could be found and *rbStart_ptr and *rbSize_ptr are set accordingly * \return Indicates if a free block of RBs of the required size could be found and *rbStart_ptr and *rbSize_ptr are set accordingly
*/ */
int find_largest_free_block(const uint16_t *vrb_map, uint16_t slbitmap, int bwp_start, int bwp_size, int *out_start);
bool get_rb_alloc(int rbSize_min, bool get_rb_alloc(int rbSize_min,
int rbSize_max, int rbSize_max,
int bwpStart, int bwpStart,
...@@ -434,11 +437,44 @@ bool get_rb_alloc(int rbSize_min, ...@@ -434,11 +437,44 @@ bool get_rb_alloc(int rbSize_min,
int *rbStart_ptr, int *rbStart_ptr,
int *rbSize_ptr); int *rbSize_ptr);
int get_mcs_from_bler(const NR_bler_options_t *bler_options, /* Scalar core of the BLER -> MCS adaptation rule. Single source of truth
const NR_mac_dir_stats_t *stats, * for the activity-guard threshold and the lower/upper hysteresis. */
NR_bler_stats_t *bler_stats, int nr_adapt_mcs_from_bler(int current_mcs,
int max_mcs, int min_mcs,
frame_t frame); int max_mcs,
float bler,
float bler_lower,
float bler_upper,
int num_sched);
bool update_bler_stats(const NR_bler_options_t *bler_options,
const NR_mac_dir_stats_t *stats,
NR_bler_stats_t *bler_stats,
frame_t frame);
float dl_pf_weight(int mcs, int mcs_table, int nrOfLayers, float avg_throughput);
uint16_t check_dl_retx_feasibility(const nr_dl_candidate_t *cand,
int tda,
const NR_tda_info_t *tda_info,
const NR_ServingCellConfigCommon_t *scc,
uint16_t max_rbSize);
bool nr_dl_validate_cce_pucch(const nr_dl_sched_params_t *params, nr_dl_candidate_t *cand);
bool commit_alloc(const nr_dl_sched_params_t *params, nr_dl_candidate_t *cand);
// Use inside the policy loops: sets RB/MCS on candidate, validates CCE/PUCCH,
// marks scheduled; continues on failure, returns on max_num_ue.
#define COMMIT_ALLOC(params, cand, rb_start_, rb_size_, mcs_, n_sched) \
do { \
(cand)->sched_pdsch.rbStart = (rb_start_); \
(cand)->sched_pdsch.rbSize = (rb_size_); \
(cand)->sched_pdsch.mcs = (mcs_); \
if (!commit_alloc(params, cand)) \
continue; \
(cand)->scheduled = true; \
(n_sched)++; \
if ((n_sched) >= (params)->max_num_ue) \
return (n_sched); \
} while (0)
int ul_buffer_index(int frame, int slot, int slots_per_frame, int size); int ul_buffer_index(int frame, int slot, int slots_per_frame, int size);
void UL_tti_req_ahead_initialization(gNB_MAC_INST *gNB, int n, int CCid, frame_t frameP, int slotP); void UL_tti_req_ahead_initialization(gNB_MAC_INST *gNB, int n, int CCid, frame_t frameP, int slotP);
...@@ -495,7 +531,11 @@ void prepare_du_configuration_update(gNB_MAC_INST *mac, ...@@ -495,7 +531,11 @@ void prepare_du_configuration_update(gNB_MAC_INST *mac,
void nr_mac_clean_cellgroup(NR_CellGroupConfig_t *cell_group); void nr_mac_clean_cellgroup(NR_CellGroupConfig_t *cell_group);
void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE_info_t *UE, NR_sched_pdsch_t *sched_pdsch); void post_process_dlsch(gNB_MAC_INST *nr_mac,
post_process_pdsch_t *pdsch,
NR_UE_info_t *UE,
NR_sched_pdsch_t *sched_pdsch,
const nr_dl_candidate_t *candidate);
void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE_info_t *UE, NR_sched_pusch_t *sched_pusch); void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE_info_t *UE, NR_sched_pusch_t *sched_pusch);
float nr_mac_get_snr(const nr_power_control_t *pc); float nr_mac_get_snr(const nr_power_control_t *pc);
......
...@@ -173,7 +173,8 @@ size_t dump_mac_stats(gNB_MAC_INST *gNB, char *output, size_t strlen, bool reset ...@@ -173,7 +173,8 @@ size_t dump_mac_stats(gNB_MAC_INST *gNB, char *output, size_t strlen, bool reset
float pucch_snr_diff = (pucch_snr * 10.0f - sched_ctrl->pucch_pc.target_snrx10) / 10.0f; float pucch_snr_diff = (pucch_snr * 10.0f - sched_ctrl->pucch_pc.target_snrx10) / 10.0f;
output = st_append(output, output = st_append(output,
end, end,
", dlsch_errors %"PRIu64", pucch0_DTX %d (SNR %.1f%+.1f dB), BLER %.5f MCS (%d) %d CCE fail %d\n", ", dlsch_errors %" PRIu64
", pucch0_DTX %d (SNR %.1f%+.1f dB), BLER %.5f MCS (%d) %d CCE fail %d, goodput %.2f Mbps\n",
stats->dl.errors, stats->dl.errors,
stats->pucch0_DTX, stats->pucch0_DTX,
pucch_snr, pucch_snr,
...@@ -181,7 +182,8 @@ size_t dump_mac_stats(gNB_MAC_INST *gNB, char *output, size_t strlen, bool reset ...@@ -181,7 +182,8 @@ size_t dump_mac_stats(gNB_MAC_INST *gNB, char *output, size_t strlen, bool reset
sched_ctrl->dl_bler_stats.bler, sched_ctrl->dl_bler_stats.bler,
UE->current_DL_BWP.mcsTableIdx, UE->current_DL_BWP.mcsTableIdx,
sched_ctrl->dl_bler_stats.mcs, sched_ctrl->dl_bler_stats.mcs,
sched_ctrl->dl_cce_fail); sched_ctrl->dl_cce_fail,
UE->dl_thr_ue_display / 1e6);
if (reset_rsrp) { if (reset_rsrp) {
stats->num_rsrp_meas = 0; stats->num_rsrp_meas = 0;
stats->cumul_rsrp = 0; stats->cumul_rsrp = 0;
...@@ -300,12 +302,19 @@ void mac_top_init_gNB(ngran_node_t node_type, ...@@ -300,12 +302,19 @@ void mac_top_init_gNB(ngran_node_t node_type,
uid_linear_allocator_init(&RC.nrmac[i]->UE_info.uid_allocator); uid_linear_allocator_init(&RC.nrmac[i]->UE_info.uid_allocator);
RC.nrmac[i]->dl_lcid_alloc = nr_dl_lcid_alloc_default;
if (get_softmodem_params()->phy_test) { if (get_softmodem_params()->phy_test) {
RC.nrmac[i]->pre_processor_dl = nr_preprocessor_phytest; RC.nrmac[i]->pre_processor_dl = nr_preprocessor_phytest;
RC.nrmac[i]->pre_processor_ul = nr_ul_preprocessor_phytest; RC.nrmac[i]->pre_processor_ul = nr_ul_preprocessor_phytest;
} else { } else {
RC.nrmac[i]->pre_processor_dl = nr_init_dlsch_preprocessor(); RC.nrmac[i]->pre_processor_dl = nr_dlsch_preprocessor;
RC.nrmac[i]->pre_processor_ul = nr_init_ulsch_preprocessor(); RC.nrmac[i]->pre_processor_ul = nr_init_ulsch_preprocessor();
RC.nrmac[i]->dl_ri_pmi_select = nr_dl_ri_pmi_select_default;
RC.nrmac[i]->dl_mcs_select = nr_dl_mcs_select_default;
RC.nrmac[i]->dl_beam_select = nr_dl_beam_select_default;
RC.nrmac[i]->dl_tda_select = nr_dl_tda_select_default;
RC.nrmac[i]->dl_rb_alloc = nr_dl_proportional_fair;
} }
if (!IS_SOFTMODEM_NOSTATS) if (!IS_SOFTMODEM_NOSTATS)
threadCreate(&RC.nrmac[i]->stats_thread, threadCreate(&RC.nrmac[i]->stats_thread,
......
...@@ -523,6 +523,7 @@ typedef struct NR_bler_stats { ...@@ -523,6 +523,7 @@ typedef struct NR_bler_stats {
float bler; float bler;
uint8_t mcs; uint8_t mcs;
uint64_t rounds[8]; uint64_t rounds[8];
int last_num_sched; // scheduling count at last BLER update (for activity guard)
} NR_bler_stats_t; } NR_bler_stats_t;
// //
...@@ -714,6 +715,8 @@ typedef struct NR_mac_dir_stats { ...@@ -714,6 +715,8 @@ typedef struct NR_mac_dir_stats {
uint32_t total_rbs_retx; uint32_t total_rbs_retx;
uint32_t num_mac_sdu; uint32_t num_mac_sdu;
uint32_t current_rbs; uint32_t current_rbs;
uint64_t prev_sdu_bytes;
frame_t last_goodput_frame;
} NR_mac_dir_stats_t; } NR_mac_dir_stats_t;
typedef struct NR_mac_stats { typedef struct NR_mac_stats {
...@@ -784,8 +787,14 @@ typedef struct NR_UE_info { ...@@ -784,8 +787,14 @@ typedef struct NR_UE_info {
measgap_config_t measgap_config; measgap_config_t measgap_config;
// UE selected beam index // UE selected beam index
uint16_t UE_beam_index; uint16_t UE_beam_index;
/// Per-SSB L1-RSRP table, indexed by SSB index (resource_id from CSI report).
/// INT16_MIN means no measurement received yet for that beam.
int16_t beam_rsrp[MAX_NUM_OF_SSB];
/// Per-SSB L1-SINR×10 table, same indexing. INT16_MIN means no data.
int16_t beam_sinr[MAX_NUM_OF_SSB];
float ul_thr_ue; float ul_thr_ue;
float dl_thr_ue; float dl_thr_ue;
float dl_thr_ue_display; ///< slow EWMA for stable display (alpha=0.001)
long pdsch_HARQ_ACK_Codebook; long pdsch_HARQ_ACK_Codebook;
bool is_redcap; bool is_redcap;
bool reestablish_rlc; bool reestablish_rlc;
...@@ -822,7 +831,8 @@ typedef struct { ...@@ -822,7 +831,8 @@ typedef struct {
nr_beam_mode_t beam_mode; nr_beam_mode_t beam_mode;
} NR_beam_info_t; } NR_beam_info_t;
#define UE_iterator(BaSe, VaR) for (NR_UE_info_t **VaR##pptr=BaSe, *VaR=*VaR##pptr; VaR; VaR=*(++VaR##pptr)) #define UE_iterator(BaSe, VaR) for (NR_UE_info_t **VaR##pptr = BaSe, *VaR = *VaR##pptr; VaR; VaR = *(++VaR##pptr))
#define FOR_EACH_CANDIDATE(VaR, ArR, N) for (__typeof__(*(ArR)) *VaR = (ArR); VaR < (ArR) + (N); VaR++)
typedef struct { typedef struct {
/// current frame /// current frame
...@@ -846,6 +856,76 @@ typedef struct { ...@@ -846,6 +856,76 @@ typedef struct {
nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu_coreset[MAX_NUM_CORESET]; nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu_coreset[MAX_NUM_CORESET];
} post_process_pusch_t; } post_process_pusch_t;
/* forward declarations for scheduling types */
struct gNB_MAC_INST_s;
typedef struct nr_dl_candidate nr_dl_candidate_t;
/// Scheduling context passed to the RB allocation policy.
/// Contains per-beam VRB maps so the policy can handle beam partitioning
/// internally (e.g. for MU-MIMO cross-beam scheduling).
typedef struct nr_dl_sched_params nr_dl_sched_params_t;
struct nr_dl_sched_params {
struct gNB_MAC_INST_s *mac; ///< MAC instance (for CCE/PUCCH validation)
int CC_id;
frame_t frame;
slot_t slot;
int num_beams; ///< number of beams
int max_num_ue; ///< max UEs to schedule
uint16_t *vrb_map[MAX_NUM_BEAM_PERIODS]; ///< per-beam VRB maps [275], mutable
int n_rb_avail[MAX_NUM_BEAM_PERIODS]; ///< available RBs per beam
int min_mcs; ///< minimum MCS from BLER config
float bler_lower; ///< BLER lower threshold (increase MCS if below)
float bler_upper; ///< BLER upper threshold (decrease MCS if above)
};
/// Per-UE scheduling candidate — read-only inputs for the policy function.
struct nr_dl_candidate {
/* ── UE identity / scheduling state (set by collect, never modified after) ── */
NR_UE_info_t *UE;
uint16_t rnti; ///< UE RNTI (convenience, avoids UE pointer dereference)
bool is_retx; ///< true = HARQ retransmission pending
int8_t retx_harq_pid; ///< HARQ PID for retx, -1 if none
int retx_rbSize; ///< RBs needed for retx, 0 for new tx
uint32_t pending_bytes; ///< total bytes waiting in RLC buffers
uint32_t pending_bytes_per_lcid[NR_MAX_NUM_LCID]; ///< per-LCID bytes waiting in RLC buffers
float avg_throughput; ///< EWMA goodput in bps (dl_thr_ue)
float bler; ///< current BLER estimate
int current_mcs; ///< current MCS state (retx: from HARQ, new tx: from BLER tracker)
int max_mcs; ///< max allowed MCS (config + UE capability)
int last_num_sched; ///< scheduled occasions in last BLER window
bool bler_updated; ///< true if BLER was refreshed this frame
int mcs_table; ///< MCS table index (from BWP config)
int bwp_start; ///< UE's BWP start
int bwp_size; ///< UE's BWP size
uint64_t fiveQI; ///< 5QI from first DRB's QoS config (0 if none)
int priority; ///< LC priority from first DRB (lower = higher priority, 0 if none)
nssai_t nssai; ///< slice/service type/differentiator from first DRB
bool skipped; ///< true if dropped by TDA/beam select (skip in downstream stages)
bool scheduled; ///< true if accepted by the RB-allocation policy
/* ── UE CSI observations (set by collect, read-only after) ─────────────── */
uint16_t cqi; ///< UE-reported wideband CQI
uint8_t csi_ri; ///< UE-reported rank indicator (0 = rank-1); forced to 0 for DCI 1_0
int csi_pm_index; ///< PM index derived from UE-reported PMI + antenna config
const int16_t *beam_rsrp; ///< per-SSB L1-RSRP; points into NR_UE_info_t::beam_rsrp. INT16_MIN = no data
const int16_t *beam_sinr; ///< per-SSB L1-SINR×10; same indexing. INT16_MIN = no data
/* ── gNB decisions (written by the named pipeline stage) ───────────────── */
/* Use NR_sched_pdsch_t for fields shared with HARQ/dispatch (mcs, rbStart,
* rbSize, nrOfLayers, pm_index, tda, tda_info, pucch_allocation).*/
NR_sched_pdsch_t sched_pdsch;
uint16_t alloc_slbitmap; ///< symbol bitmap derived from sched_pdsch.tda_info
/* dl_beam_select: beam selection */
int alloc_beam_dir; ///< beam direction index (initialised from UE->UE_beam_index; may be overridden)
int alloc_beam_idx; ///< hardware beam structure index, set by beam_allocation_procedure
bool alloc_new_beam; ///< true if beam alloc claimed a fresh slot (release via reset_beam_status if unscheduled)
/* commit_alloc: CCE/PUCCH validation */
int alloc_cce_index; ///< CCE index for PDCCH
int alloc_aggregation_level; ///< PDCCH aggregation level
NR_sched_pdcch_t alloc_sched_pdcch; ///< PDCCH scheduling info
};
/* forward declaration to use in nr_pp_impl_dl */ /* forward declaration to use in nr_pp_impl_dl */
struct gNB_MAC_INST_s; struct gNB_MAC_INST_s;
typedef struct gNB_MAC_INST_s gNB_MAC_INST; typedef struct gNB_MAC_INST_s gNB_MAC_INST;
...@@ -853,7 +933,49 @@ typedef struct gNB_MAC_INST_s gNB_MAC_INST; ...@@ -853,7 +933,49 @@ typedef struct gNB_MAC_INST_s gNB_MAC_INST;
typedef void (*nr_pp_impl_dl)(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pp_pdsch); typedef void (*nr_pp_impl_dl)(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pp_pdsch);
typedef void (*nr_pp_impl_ul)(gNB_MAC_INST *nr_mac, post_process_pusch_t *pp_pusch); typedef void (*nr_pp_impl_ul)(gNB_MAC_INST *nr_mac, post_process_pusch_t *pp_pusch);
typedef struct { /// RI/PMI selection: sets nrOfLayers and pm_index per candidate from CSI feedback.
/// For retransmissions, nrOfLayers must match the original transmission.
/// Custom implementations may use SRS reciprocity to override the UE's reported RI/PMI.
typedef void (*nr_dl_ri_pmi_select_fn)(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates);
/// MCS adaptation: sets sched_pdsch.mcs from BLER state for every candidate.
/// Called for all candidates (including those that won't get scheduled) so
/// BLER-based MCS ramps even for UEs that fail CCE. Also persists the
/// decision to dl_bler_stats.mcs for continuity across slots.
typedef void (*nr_dl_mcs_select_fn)(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates);
/// Beam allocation: assigns beam structure index to each candidate.
/// beam_index_list maps SSB id -> beam index (same as mac->beam_index_list);
/// custom implementations can use it with cand->beam_rsrp[] to pick the best beam.
typedef int (*nr_dl_beam_select_fn)(NR_beam_info_t *beam_info,
const int16_t *beam_index_list,
nr_dl_candidate_t *candidates,
int n_candidates,
frame_t frame,
slot_t slot,
int slots_per_frame);
/// TDA selection: assigns tda/tda_info/slbitmap per candidate.
/// Returns the number of candidates with a valid TDA (compacts invalids out).
typedef int (
*nr_dl_tda_select_fn)(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates, frame_t frame, slot_t slot);
/// Scheduling policy: decides PRB + MCS for candidates across all beams.
/// The beam loop is inside the policy so it can do cross-beam scheduling (e.g. MU-MIMO).
/// Writes sched_pdsch.rbStart/rbSize/mcs and cce/pucch fields on scheduled candidates.
/// Sets candidate->scheduled = true for each accepted UE; returns the count.
typedef int (*nr_dl_rb_alloc_fn)(const nr_dl_sched_params_t *params, nr_dl_candidate_t *candidates, int n_candidates);
/// Per-LCID byte allocation: decides how many bytes each LCID gets within
/// the available TBS for an initial transmission. Called during MAC PDU
/// generation. Writes lcid_alloc[lcid] = max data bytes for that LCID.
/// The execution loop caps actual RLC requests to these budgets.
typedef void (*nr_dl_lcid_alloc_fn)(const gNB_MAC_INST *mac,
const nr_dl_candidate_t *candidate,
int tbs_available,
int lcid_alloc[NR_MAX_NUM_LCID]);
typedef struct f1_config_t {
f1ap_setup_req_t *setup_req; f1ap_setup_req_t *setup_req;
f1ap_setup_resp_t *setup_resp; f1ap_setup_resp_t *setup_resp;
uint32_t gnb_id; // associated gNB's ID, not used in DU itself uint32_t gnb_id; // associated gNB's ID, not used in DU itself
...@@ -962,6 +1084,17 @@ typedef struct gNB_MAC_INST_s { ...@@ -962,6 +1084,17 @@ typedef struct gNB_MAC_INST_s {
/// UL preprocessor for differentiated scheduling /// UL preprocessor for differentiated scheduling
nr_pp_impl_ul pre_processor_ul; nr_pp_impl_ul pre_processor_ul;
/// DL scheduling pipeline function pointers
nr_dl_ri_pmi_select_fn dl_ri_pmi_select;
nr_dl_tda_select_fn dl_tda_select;
nr_dl_beam_select_fn dl_beam_select;
nr_dl_mcs_select_fn dl_mcs_select;
nr_dl_rb_alloc_fn dl_rb_alloc;
nr_dl_lcid_alloc_fn dl_lcid_alloc;
/// Optional state persistence for scheduling policies.
void *sched_stateful_data;
nr_mac_config_t radio_config; nr_mac_config_t radio_config;
nr_rlc_configuration_t rlc_config; nr_rlc_configuration_t rlc_config;
...@@ -977,7 +1110,7 @@ typedef struct gNB_MAC_INST_s { ...@@ -977,7 +1110,7 @@ typedef struct gNB_MAC_INST_s {
uint16_t min_grant_prb; uint16_t min_grant_prb;
bool identity_pm; bool identity_pm;
int precoding_matrix_size[NR_MAX_NB_LAYERS]; int precoding_matrix_size[NR_MAX_NB_LAYERS];
int beam_index_list[MAX_NUM_OF_SSB]; int16_t beam_index_list[MAX_NUM_OF_SSB];
NR_sched_pdsch_t sib1_pdsch[MAX_NUM_OF_SSB]; NR_sched_pdsch_t sib1_pdsch[MAX_NUM_OF_SSB];
/// dedicate UL TDA, common for all UEs /// dedicate UL TDA, common for all UEs
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment