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;
|
float coef;
|
||||||
NR_UE_info_t * UE;
|
NR_UE_info_t * UE;
|
||||||
int selected_mcs;
|
int selected_mcs;
|
||||||
|
bool mumimo_paired; /* nakaolab: already allocated as MU-MIMO partner; main loop must skip */
|
||||||
} UEsched_t;
|
} 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)
|
static int comparator(const void *p, const void *q)
|
||||||
{
|
{
|
||||||
const UEsched_t *pp = p;
|
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 */
|
/* Loop UE_sched to find max coeff and allocate transmission */
|
||||||
while (iterator->UE != NULL) {
|
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;
|
NR_UE_sched_ctrl_t *sched_ctrl = &iterator->UE->UE_sched_ctrl;
|
||||||
const uint16_t rnti = iterator->UE->rnti;
|
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.tb_size,
|
||||||
&sched_pdsch.rbSize);
|
&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);
|
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 */
|
/* transmissions: directly allocate */
|
||||||
n_rb_sched[beam.idx] -= sched_pdsch.rbSize;
|
n_rb_sched[beam.idx] -= sched_pdsch.rbSize;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user