mac: MU-MIMO DL co-scheduling for orthogonal-PMI UEs (nakaolab)
Adds opt-in MU-MIMO downlink co-scheduling to the production
proportional-fair scheduler (pf_dl). When OAI_MU_MIMO=1 is set in
the gNB process env, after the leader UE is allocated we look ahead
in the PF-sorted candidate list for an orthogonal-PMI partner. If
found and the partner can secure its own CCE+PUCCH, both UEs share
the same RB allocation with distinct DMRS CDM groups (port 1000 vs
port 1001) and are forced to the same MCS/TBS.
Orthogonality test (4-port Type I rank-1 codebook, N1=2, N2=1):
same x1 (same beam), x2 differing by 2 (co-phasing pairs {0,2} or
{1,3}). Both UEs must report rank-1.
Default OFF — set OAI_MU_MIMO=1 to enable. Builds on the upstream
FAPI/PHY MU-MIMO plumbing (commits 3707d52c95, 498d241d75,
9952996c5d) which carries antenna-port and DMRS-CDM metadata down
to the PHY transmit path.
This commit is contained in:
@@ -593,8 +593,46 @@ typedef struct UEsched_s {
|
||||
float coef;
|
||||
NR_UE_info_t * UE;
|
||||
int selected_mcs;
|
||||
bool mumimo_paired; /* nakaolab: already allocated as MU-MIMO partner; main loop must skip */
|
||||
} UEsched_t;
|
||||
|
||||
/* nakaolab: cached MU-MIMO enable state.
|
||||
* Read once via OAI_MU_MIMO env var. 0 = SU-MIMO (default), 1 = MU-MIMO co-scheduling. */
|
||||
static int mumimo_enabled(void)
|
||||
{
|
||||
static int cached = -1;
|
||||
if (cached < 0) {
|
||||
const char *e = getenv("OAI_MU_MIMO");
|
||||
cached = (e && atoi(e) > 0) ? 1 : 0;
|
||||
if (cached)
|
||||
LOG_W(NR_MAC, "[nakaolab] MU-MIMO downlink scheduling ENABLED (OAI_MU_MIMO=1)\n");
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
/* nakaolab: orthogonal-PMI test for 4-port Type I rank-1 codebook (N1=2, N2=1).
|
||||
* The codebook codewords are W = (1/sqrt(4)) [v_l ; theta_n * v_l] where v_l is
|
||||
* the spatial vector (indexed by pmi_x1) and theta_n in {1, j, -1, -j} is the
|
||||
* co-phasing (indexed by pmi_x2 in {0,1,2,3}).
|
||||
* Two codewords W1, W2 are orthogonal iff:
|
||||
* - same x1 (same beam) AND x2 differ by 2 (theta_n + theta_n' = 0)
|
||||
* -> orthogonal pairs are {0,2} and {1,3}
|
||||
* For different x1 we conservatively don't pair (DFT vectors at adjacent indices
|
||||
* are not strictly orthogonal). This catches the common case at low UE mobility
|
||||
* where multiple UEs report the same dominant beam with different polarizations. */
|
||||
static bool pmi_is_orthogonal(const NR_UE_info_t *a, const NR_UE_info_t *b)
|
||||
{
|
||||
const struct CRI_RI_LI_PMI_CQI *ra = &a->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report;
|
||||
const struct CRI_RI_LI_PMI_CQI *rb = &b->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report;
|
||||
/* Both UEs must report rank-1; other ranks need codebook-specific orthogonality. */
|
||||
if (ra->ri != 0 || rb->ri != 0)
|
||||
return false;
|
||||
if (ra->pmi_x1 != rb->pmi_x1)
|
||||
return false;
|
||||
/* x2 in {0,1,2,3}; orthogonal pairs are (0,2) and (1,3). */
|
||||
return ((ra->pmi_x2 ^ rb->pmi_x2) == 2);
|
||||
}
|
||||
|
||||
static int comparator(const void *p, const void *q)
|
||||
{
|
||||
const UEsched_t *pp = p;
|
||||
@@ -735,6 +773,12 @@ static void pf_dl(gNB_MAC_INST *mac,
|
||||
/* Loop UE_sched to find max coeff and allocate transmission */
|
||||
while (iterator->UE != NULL) {
|
||||
|
||||
/* nakaolab: skip UEs already allocated as a MU-MIMO partner of an earlier UE */
|
||||
if (iterator->mumimo_paired) {
|
||||
iterator++;
|
||||
continue;
|
||||
}
|
||||
|
||||
NR_UE_sched_ctrl_t *sched_ctrl = &iterator->UE->UE_sched_ctrl;
|
||||
const uint16_t rnti = iterator->UE->rnti;
|
||||
|
||||
@@ -877,8 +921,122 @@ static void pf_dl(gNB_MAC_INST *mac,
|
||||
&sched_pdsch.tb_size,
|
||||
&sched_pdsch.rbSize);
|
||||
|
||||
/* nakaolab MU-MIMO: try to find an orthogonal-PMI partner UE that can ride on
|
||||
* the same RB allocation as this UE. The partner is given a separate DCI with
|
||||
* its own CCE, and a different DMRS CDM group so the receiver can separate
|
||||
* the two layers. Both UEs are forced to the same MCS/TBS (we trust the leader
|
||||
* UE's PF coefficient and channel report). */
|
||||
UEsched_t *partner = NULL;
|
||||
int partner_CCE = -1;
|
||||
int partner_alloc = -1;
|
||||
|
||||
if (mumimo_enabled() && remainUEs[beam.idx] > 0) {
|
||||
/* For MU-MIMO, the leader UE's DMRS group must be 2 so the second group is
|
||||
* available for the partner. Patch the just-built sched_pdsch in place. */
|
||||
for (UEsched_t *cand = iterator + 1; cand->UE != NULL; cand++) {
|
||||
if (cand->mumimo_paired)
|
||||
continue;
|
||||
if (cand->UE->UE_sched_ctrl.available_dl_harq.head < 0)
|
||||
continue;
|
||||
if (!pmi_is_orthogonal(iterator->UE, cand->UE))
|
||||
continue;
|
||||
/* Candidate's beam must match ours so the precoding is on the same TRX
|
||||
* resources. (Different beams would also work but our config has a
|
||||
* single beam pool.) */
|
||||
NR_beam_alloc_t pbeam = beam_allocation_procedure(&mac->beam_info, frame, slot,
|
||||
cand->UE->UE_beam_index,
|
||||
slots_per_frame);
|
||||
if (pbeam.idx != beam.idx) {
|
||||
reset_beam_status(&mac->beam_info, frame, slot, cand->UE->UE_beam_index,
|
||||
slots_per_frame, pbeam.new_beam);
|
||||
continue;
|
||||
}
|
||||
/* Need a free CCE for the partner's own DCI. */
|
||||
partner_CCE = get_cce_index(mac, CC_id, slot, cand->UE->rnti,
|
||||
&cand->UE->UE_sched_ctrl.aggregation_level,
|
||||
pbeam.idx,
|
||||
cand->UE->UE_sched_ctrl.search_space,
|
||||
cand->UE->UE_sched_ctrl.coreset,
|
||||
&cand->UE->UE_sched_ctrl.sched_pdcch,
|
||||
cand->UE->UE_sched_ctrl.pdcch_cl_adjust);
|
||||
if (partner_CCE < 0) {
|
||||
reset_beam_status(&mac->beam_info, frame, slot, cand->UE->UE_beam_index,
|
||||
slots_per_frame, pbeam.new_beam);
|
||||
continue;
|
||||
}
|
||||
/* And a PUCCH for HARQ feedback. */
|
||||
if (!get_FeedbackDisabled(cand->UE->sc_info.downlinkHARQ_FeedbackDisabled_r17,
|
||||
cand->UE->UE_sched_ctrl.available_dl_harq.head)) {
|
||||
int p_pucch = nr_get_pucch_resource(cand->UE->UE_sched_ctrl.coreset,
|
||||
cand->UE->current_UL_BWP.pucch_Config,
|
||||
partner_CCE);
|
||||
partner_alloc = nr_acknack_scheduling(mac, cand->UE, frame, slot,
|
||||
cand->UE->UE_beam_index, p_pucch, 0);
|
||||
if (partner_alloc < 0) {
|
||||
reset_beam_status(&mac->beam_info, frame, slot, cand->UE->UE_beam_index,
|
||||
slots_per_frame, pbeam.new_beam);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
partner = cand;
|
||||
LOG_D(NR_MAC, "[nakaolab MU-MIMO] %4d.%2d pair RNTI %04x (x1=%d x2=%d) + RNTI %04x (x1=%d x2=%d) on RBs [%d..%d]\n",
|
||||
frame, slot,
|
||||
iterator->UE->rnti,
|
||||
iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1,
|
||||
iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2,
|
||||
cand->UE->rnti,
|
||||
cand->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1,
|
||||
cand->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2,
|
||||
sched_pdsch.rbStart, sched_pdsch.rbStart + sched_pdsch.rbSize - 1);
|
||||
break;
|
||||
}
|
||||
if (partner != NULL) {
|
||||
/* Lift the leader's DMRS group count so the receiver can separate. */
|
||||
sched_pdsch.dmrs_parms.numDmrsCdmGrpsNoData = 2;
|
||||
}
|
||||
}
|
||||
|
||||
post_process_dlsch(mac, pp_pdsch, iterator->UE, &sched_pdsch);
|
||||
|
||||
/* nakaolab: now allocate the MU-MIMO partner on the same RBs. */
|
||||
if (partner != NULL) {
|
||||
NR_UE_sched_ctrl_t *p_sched_ctrl = &partner->UE->UE_sched_ctrl;
|
||||
NR_UE_DL_BWP_t *p_dl_bwp = &partner->UE->current_DL_BWP;
|
||||
|
||||
p_sched_ctrl->cce_index = partner_CCE;
|
||||
fill_pdcch_vrb_map(mac, CC_id, &p_sched_ctrl->sched_pdcch, partner_CCE,
|
||||
p_sched_ctrl->aggregation_level, beam.idx);
|
||||
|
||||
const int p_l = 1; /* MU-MIMO mode: rank-1 per UE */
|
||||
NR_sched_pdsch_t p_sched_pdsch = {
|
||||
.rbStart = sched_pdsch.rbStart,
|
||||
.rbSize = sched_pdsch.rbSize,
|
||||
.mcs = sched_pdsch.mcs, /* match leader */
|
||||
.R = sched_pdsch.R,
|
||||
.Qm = sched_pdsch.Qm,
|
||||
.tb_size = sched_pdsch.tb_size,
|
||||
.dl_harq_pid = p_sched_ctrl->available_dl_harq.head,
|
||||
.pucch_allocation = partner_alloc,
|
||||
.pm_index = get_pm_index(mac, partner->UE, p_dl_bwp->dci_format, p_l,
|
||||
mac->radio_config.pdsch_AntennaPorts.XP),
|
||||
.nrOfLayers = p_l,
|
||||
.bwp_info = get_pdsch_bwp_start_size(mac, partner->UE),
|
||||
.dmrs_parms = get_dl_dmrs_params(scc, p_dl_bwp, &tda_info, p_l),
|
||||
.time_domain_allocation = tda,
|
||||
.tda_info = tda_info,
|
||||
.action = NULL,
|
||||
};
|
||||
/* Partner uses DMRS CDM group 1 (port 1001). dmrs_ports_id is a bitmask;
|
||||
* 2 = bit 1 set -> uses the second port pair. numDmrsCdmGrpsNoData=2 is
|
||||
* required so DMRS pilots from both groups are reserved. */
|
||||
p_sched_pdsch.dmrs_parms.dmrs_ports_id = 2;
|
||||
p_sched_pdsch.dmrs_parms.numDmrsCdmGrpsNoData = 2;
|
||||
|
||||
post_process_dlsch(mac, pp_pdsch, partner->UE, &p_sched_pdsch);
|
||||
partner->mumimo_paired = true;
|
||||
remainUEs[beam.idx]--;
|
||||
}
|
||||
|
||||
/* transmissions: directly allocate */
|
||||
n_rb_sched[beam.idx] -= sched_pdsch.rbSize;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user