/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

/*! \file eNB_scheduler.c
 * \brief eNB scheduler top level function operates on per subframe basis
 * \author  Navid Nikaein and Raymond Knopp
 * \date 2010 - 2014
 * \email: navid.nikaein@eurecom.fr
 * \version 0.5
 * @ingroup _mac

 */

#include "assertions.h"
#include "targets/RT/USER/lte-softmodem.h"
#include "LAYER2/MAC/mac.h"
#include "LAYER2/MAC/mac_extern.h"

#include "LAYER2/MAC/mac_proto.h"
#include "common/utils/LOG/log.h"
#include "nfapi/oai_integration/vendor_ext.h"
#include "common/utils/LOG/vcd_signal_dumper.h"
#include "UTIL/OPT/opt.h"
#include "OCG.h"
#include "OCG_extern.h"

#include "RRC/LTE/rrc_extern.h"
#include "RRC/NR/nr_rrc_extern.h"
#include "RRC/L2_INTERFACE/openair_rrc_L2_interface.h"

//#include "LAYER2/MAC/pre_processor.c"
#include "pdcp.h"

/* for fair round robin SCHED */
#include "eNB_scheduler_fairRR.h"

#include "intertask_interface.h"

#include "assertions.h"

#define ENABLE_MAC_PAYLOAD_DEBUG
#define DEBUG_eNB_SCHEDULER 1

extern RAN_CONTEXT_t RC;


uint16_t pdcch_order_table[6] = { 31, 31, 511, 2047, 2047, 8191 };

//-----------------------------------------------------------------------------
/*
 * Schedule periodic SRS
 */
void schedule_SRS(module_id_t module_idP,
                  frame_t frameP,
                  sub_frame_t subframeP)
//-----------------------------------------------------------------------------
{
  int CC_id = 0;
  int UE_id = -1;
  uint8_t TSFC = 0;
  uint8_t srs_SubframeConfig = 0;
  uint16_t srsPeriodicity = 0;
  uint16_t srsOffset = 0;
  uint16_t deltaTSFC = 0;  // bitmap
  // table for TSFC (Period) and deltaSFC (offset)
  const uint16_t deltaTSFCTabType1[15][2] = { {1, 1}, {1, 2}, {2, 2}, {1, 5}, {2, 5}, {4, 5}, {8, 5}, {3, 5}, {12, 5}, {1, 10}, {2, 10}, {4, 10}, {8, 10}, {351, 10}, {383, 10} };  // Table 5.5.3.3-2 3GPP 36.211 FDD
  const uint16_t deltaTSFCTabType2[14][2] = { {2, 5}, {6, 5}, {10, 5}, {18, 5}, {14, 5}, {22, 5}, {26, 5}, {30, 5}, {70, 10}, {74, 10}, {194, 10}, {326, 10}, {586, 10}, {210, 10} }; // Table 5.5.3.3-2 3GPP 36.211 TDD
  eNB_MAC_INST *eNB = RC.mac[module_idP];
  UE_info_t *UE_info = &eNB->UE_info;
  nfapi_ul_config_request_body_t *ul_req = NULL;
  UE_sched_ctrl_t *UE_scheduling_control = NULL;
  COMMON_channels_t *cc = eNB->common_channels;
  LTE_SoundingRS_UL_ConfigCommon_t *soundingRS_UL_ConfigCommon = NULL;
  struct LTE_SoundingRS_UL_ConfigDedicated *soundingRS_UL_ConfigDedicated = NULL;

  for (CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) {
    soundingRS_UL_ConfigCommon = &(cc[CC_id].radioResourceConfigCommon->soundingRS_UL_ConfigCommon);

    /* Check if SRS is enabled in this frame/subframe */
    if (soundingRS_UL_ConfigCommon) {
      srs_SubframeConfig = soundingRS_UL_ConfigCommon->choice.setup.srs_SubframeConfig;

      if (cc[CC_id].tdd_Config == NULL) {  // FDD
        deltaTSFC = deltaTSFCTabType1[srs_SubframeConfig][0];
        TSFC = deltaTSFCTabType1[srs_SubframeConfig][1];
      } else {  // TDD
        deltaTSFC = deltaTSFCTabType2[srs_SubframeConfig][0];
        TSFC = deltaTSFCTabType2[srs_SubframeConfig][1];
      }

      /* Sounding reference signal subframes are the subframes satisfying ns/2 mod TSFC (- deltaTSFC) */
      uint16_t tmp = (subframeP % TSFC);

      if ((1 << tmp) & deltaTSFC) {
        /* This is an SRS subframe, loop over UEs */
        for (UE_id = 0; UE_id < MAX_MOBILES_PER_ENB; UE_id++) {
          if (!UE_info->active[UE_id]) {
            continue;
          }

          /* Drop the allocation if the UE hasn't send RRCConnectionSetupComplete yet */
          if (mac_eNB_get_rrc_status(module_idP, UE_RNTI(module_idP, UE_id)) < RRC_CONNECTED) {
            continue;
          }

          if(UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated == NULL) {
            LOG_E(MAC,"physicalConfigDedicated is null for UE %d\n",UE_id);
            printf("physicalConfigDedicated is null for UE %d\n",UE_id);
            return;
          }

          /* CDRX condition on Active Time and SRS type-0 report (36.321 5.7) */
          UE_scheduling_control = &(UE_info->UE_sched_ctrl[UE_id]);

          /* Test if Active Time not running since 6+ subframes */
          if (UE_scheduling_control->cdrx_configured == true && UE_scheduling_control->in_active_time == false) {
            /*
             * TODO: 6+ subframes condition not checked here
             */
            continue;
          }

          ul_req = &(eNB->UL_req[CC_id].ul_config_request_body);

          if ((soundingRS_UL_ConfigDedicated = UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated->soundingRS_UL_ConfigDedicated) != NULL) {
            if (soundingRS_UL_ConfigDedicated->present == LTE_SoundingRS_UL_ConfigDedicated_PR_setup) {
              get_srs_pos(&cc[CC_id],
                          soundingRS_UL_ConfigDedicated->choice.setup.srs_ConfigIndex,
                          &srsPeriodicity,
                          &srsOffset);

              if (((10 * frameP + subframeP) % srsPeriodicity) == srsOffset) {
                // Program SRS
                ul_req->srs_present = 1;
                nfapi_ul_config_request_pdu_t *ul_config_pdu = &(ul_req->ul_config_pdu_list[ul_req->number_of_pdus]);
                memset((void *) ul_config_pdu, 0, sizeof(nfapi_ul_config_request_pdu_t));
                ul_config_pdu->pdu_type =  NFAPI_UL_CONFIG_SRS_PDU_TYPE;
                ul_config_pdu->pdu_size =  2 + (uint8_t) (2 + sizeof(nfapi_ul_config_srs_pdu));
                ul_config_pdu->srs_pdu.srs_pdu_rel8.tl.tag = NFAPI_UL_CONFIG_REQUEST_SRS_PDU_REL8_TAG;
                ul_config_pdu->srs_pdu.srs_pdu_rel8.size = (uint8_t)sizeof(nfapi_ul_config_srs_pdu);
                ul_config_pdu->srs_pdu.srs_pdu_rel8.rnti = UE_info->UE_template[CC_id][UE_id].rnti;
                ul_config_pdu->srs_pdu.srs_pdu_rel8.srs_bandwidth = soundingRS_UL_ConfigDedicated->choice.setup.srs_Bandwidth;
                ul_config_pdu->srs_pdu.srs_pdu_rel8.frequency_domain_position = soundingRS_UL_ConfigDedicated->choice.setup.freqDomainPosition;
                ul_config_pdu->srs_pdu.srs_pdu_rel8.srs_hopping_bandwidth = soundingRS_UL_ConfigDedicated->choice.setup.srs_HoppingBandwidth;;
                ul_config_pdu->srs_pdu.srs_pdu_rel8.transmission_comb = soundingRS_UL_ConfigDedicated->choice.setup.transmissionComb;
                ul_config_pdu->srs_pdu.srs_pdu_rel8.i_srs = soundingRS_UL_ConfigDedicated->choice.setup.srs_ConfigIndex;
                ul_config_pdu->srs_pdu.srs_pdu_rel8.sounding_reference_cyclic_shift = soundingRS_UL_ConfigDedicated->choice.setup.cyclicShift;
                eNB->UL_req[CC_id].sfn_sf = (frameP << 4) + subframeP;
                eNB->UL_req[CC_id].header.message_id = NFAPI_UL_CONFIG_REQUEST;
                ul_req->number_of_pdus++;
              } // if (((10*frameP+subframeP) % srsPeriodicity) == srsOffset)
            } // if (soundingRS_UL_ConfigDedicated->present == SoundingRS_UL_ConfigDedicated_PR_setup)
          } // if ((soundingRS_UL_ConfigDedicated = UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated->soundingRS_UL_ConfigDedicated)!=NULL)
        } // end for loop on UE_id
      } // if((1<<tmp) & deltaTSFC)
    } // SRS config not NULL
  } // end for loop on CC_id
}

//-----------------------------------------------------------------------------
/*
* Schedule the CSI (CQI/PMI/RI/PTI/CRI) periodic reception
*/
void schedule_CSI(module_id_t module_idP,
                  frame_t frameP,
                  sub_frame_t subframeP)
//-----------------------------------------------------------------------------
{
  int                            CC_id = 0;
  int                            UE_id = 0;
  int                            H = 0;
  uint16_t                       Npd = 0;
  uint16_t                       N_OFFSET_CQI = 0;
  struct LTE_CQI_ReportPeriodic  *cqi_ReportPeriodic = NULL;
  eNB_MAC_INST                   *eNB = RC.mac[module_idP];
  UE_info_t                      *UE_info = &eNB->UE_info;
  COMMON_channels_t              *cc = NULL;
  nfapi_ul_config_request_body_t *ul_req = NULL;
  UE_sched_ctrl_t *UE_scheduling_control = NULL;

  for (CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) {
    cc = &eNB->common_channels[CC_id];

    for (UE_id = 0; UE_id < MAX_MOBILES_PER_ENB; UE_id++) {
      if (UE_info->active[UE_id] == false) {
        continue;
      }

      /* Drop the allocation if the UE hasn't sent RRCConnectionSetupComplete yet */
      if (mac_eNB_get_rrc_status(module_idP, UE_RNTI(module_idP, UE_id)) < RRC_CONNECTED) {
        continue;
      }

      AssertFatal(UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated != NULL,
                  "physicalConfigDedicated is null for UE %d\n",
                  UE_id);
      /*
      * CDRX condition on Active Time and CSI report on PUCCH (36.321 5.7).
      * Here we consider classic periodic reports on PUCCH without PUSCH simultaneous transmission condition.
      * TODO: add the handling or test on simultaneous PUCCH/PUSCH transmission
      */
      UE_scheduling_control = &(UE_info->UE_sched_ctrl[UE_id]);

      if (UE_scheduling_control->cdrx_configured == true) {
        /* Test if CQI masking activated */
        if (UE_scheduling_control->cqi_mask_boolean == true) {
          // CQI masking => test if onDurationTime not running since 6+ subframe
          if (UE_scheduling_control->on_duration_timer == 0) {
            /*
             * TODO: 6+ subframes condition not checked here
             */
            continue;
          }
        } else { // No CQI masking => test if Active Time not running since 6+ subframe
          if (UE_scheduling_control->in_active_time == false) {
            /*
             * TODO: 6+ subframes condition not checked here
             */
            continue;
          }
        }
      }

      ul_req = &(eNB->UL_req[CC_id].ul_config_request_body);

      if (UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated->cqi_ReportConfig != NULL) {
        cqi_ReportPeriodic = UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated->cqi_ReportConfig->cqi_ReportPeriodic;

        if (cqi_ReportPeriodic != NULL) {
          /* Rel8 Periodic CSI (CQI/PMI/RI) reporting */
          if (cqi_ReportPeriodic->present != LTE_CQI_ReportPeriodic_PR_release) {
            get_csi_params(cc, cqi_ReportPeriodic, &Npd, &N_OFFSET_CQI, &H);

            if ((((frameP * 10) + subframeP) % Npd) == N_OFFSET_CQI) {  // CQI periodic opportunity
              UE_scheduling_control->feedback_cnt[CC_id] = (((frameP * 10) + subframeP) / Npd) % H;
              // Program CQI
              nfapi_ul_config_request_pdu_t *ul_config_pdu = &ul_req->ul_config_pdu_list[ul_req->number_of_pdus];
              memset((void *) ul_config_pdu, 0, sizeof(nfapi_ul_config_request_pdu_t));
              ul_config_pdu->pdu_type                                                          = NFAPI_UL_CONFIG_UCI_CQI_PDU_TYPE;
              ul_config_pdu->pdu_size                                                          = 2 + (uint8_t) (2 + sizeof(nfapi_ul_config_uci_cqi_pdu));
              ul_config_pdu->uci_cqi_pdu.ue_information.ue_information_rel8.tl.tag             = NFAPI_UL_CONFIG_REQUEST_UE_INFORMATION_REL8_TAG;
              ul_config_pdu->uci_cqi_pdu.ue_information.ue_information_rel8.rnti               = UE_info->UE_template[CC_id][UE_id].rnti;
              ul_config_pdu->uci_cqi_pdu.cqi_information.cqi_information_rel8.tl.tag           = NFAPI_UL_CONFIG_REQUEST_CQI_INFORMATION_REL8_TAG;
              ul_config_pdu->uci_cqi_pdu.cqi_information.cqi_information_rel8.pucch_index      = cqi_ReportPeriodic->choice.setup.cqi_PUCCH_ResourceIndex;
              ul_config_pdu->uci_cqi_pdu.cqi_information.cqi_information_rel8.dl_cqi_pmi_size  = get_rel8_dl_cqi_pmi_size(&UE_info->UE_sched_ctrl[UE_id], CC_id, cc, get_tmode(module_idP, CC_id, UE_id),
                  cqi_ReportPeriodic);
              ul_req->number_of_pdus++;
              ul_req->tl.tag                                                                   = NFAPI_UL_CONFIG_REQUEST_BODY_TAG;
              // PUT rel10-13 UCI options here
            } else if (cqi_ReportPeriodic->choice.setup.ri_ConfigIndex != NULL) {
              if ((((frameP * 10) + subframeP) % ((H * Npd) << (*cqi_ReportPeriodic->choice.setup.ri_ConfigIndex / 161))) ==
                  N_OFFSET_CQI + (*cqi_ReportPeriodic->choice.setup.ri_ConfigIndex % 161)) {  // RI opportunity
                // Program RI
                nfapi_ul_config_request_pdu_t *ul_config_pdu = &ul_req->ul_config_pdu_list[ul_req->number_of_pdus];
                memset((void *) ul_config_pdu, 0, sizeof(nfapi_ul_config_request_pdu_t));
                ul_config_pdu->pdu_type                                                          = NFAPI_UL_CONFIG_UCI_CQI_PDU_TYPE;
                ul_config_pdu->pdu_size                                                          = 2 + (uint8_t) (2 + sizeof(nfapi_ul_config_uci_cqi_pdu));
                ul_config_pdu->uci_cqi_pdu.ue_information.ue_information_rel8.tl.tag             = NFAPI_UL_CONFIG_REQUEST_UE_INFORMATION_REL8_TAG;
                ul_config_pdu->uci_cqi_pdu.ue_information.ue_information_rel8.rnti               = UE_info->UE_template[CC_id][UE_id].rnti;
                ul_config_pdu->uci_cqi_pdu.cqi_information.cqi_information_rel8.tl.tag           = NFAPI_UL_CONFIG_REQUEST_CQI_INFORMATION_REL8_TAG;
                ul_config_pdu->uci_cqi_pdu.cqi_information.cqi_information_rel8.pucch_index      = cqi_ReportPeriodic->choice.setup.cqi_PUCCH_ResourceIndex;
                ul_config_pdu->uci_cqi_pdu.cqi_information.cqi_information_rel8.dl_cqi_pmi_size  = (cc->p_eNB == 2) ? 1 : 2;
                RC.mac[module_idP]->UL_req[CC_id].sfn_sf                                         = (frameP << 4) + subframeP;
                ul_req->number_of_pdus++;
                ul_req->tl.tag                                                                   = NFAPI_UL_CONFIG_REQUEST_BODY_TAG;
              }
            }
          } // if CSI Periodic is not release state
        } // if (cqi_ReportPeriodic != NULL)
      } // if cqi_ReportConfig != NULL
    } // for (UE_id=UE_info->head; UE_id>=0; UE_id=UE_info->next[UE_id]) {
  } // for (CC_id=0; CC_id<MAX_NUM_CCs; CC_id++) {
}

//-----------------------------------------------------------------------------
/*
* Schedule a possible Scheduling Request reception
*/
void
schedule_SR (module_id_t module_idP,
             frame_t frameP,
             sub_frame_t subframeP)
//-----------------------------------------------------------------------------
{
  int skip_ue = 0;
  int is_harq = 0;
  int pdu_list_index = 0;
  eNB_MAC_INST *eNB = RC.mac[module_idP];
  UE_info_t *UE_info = &eNB->UE_info;
  nfapi_ul_config_request_t      *ul_req = NULL;
  nfapi_ul_config_request_body_t *ul_req_body = NULL;
  LTE_SchedulingRequestConfig_t  *SRconfig = NULL;
  nfapi_ul_config_sr_information sr;
  memset(&sr, 0, sizeof(sr));

  for (int CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) {
    eNB->UL_req[CC_id].sfn_sf = (frameP << 4) + subframeP;

    for (int UE_id = 0; UE_id < MAX_MOBILES_PER_ENB; UE_id++) {
      if (!UE_info->active[UE_id]) {
        continue;
      }

      if (UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated == NULL) continue;

      if ((SRconfig = UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated->schedulingRequestConfig) != NULL) {
        if (SRconfig->present == LTE_SchedulingRequestConfig_PR_setup) {
          if (SRconfig->choice.setup.sr_ConfigIndex <= 4) {          // 5 ms SR period
            if ((subframeP % 5) != SRconfig->choice.setup.sr_ConfigIndex) continue;
          } else if (SRconfig->choice.setup.sr_ConfigIndex <= 14) {  // 10 ms SR period
            if (subframeP != (SRconfig->choice.setup.sr_ConfigIndex - 5)) continue;
          } else if (SRconfig->choice.setup.sr_ConfigIndex <= 34) {  // 20 ms SR period
            if ((10 * (frameP & 1) + subframeP) != (SRconfig->choice.setup.sr_ConfigIndex - 15)) continue;
          } else if (SRconfig->choice.setup.sr_ConfigIndex <= 74) {  // 40 ms SR period
            if ((10 * (frameP & 3) + subframeP) != (SRconfig->choice.setup.sr_ConfigIndex - 35)) continue;
          } else if (SRconfig->choice.setup.sr_ConfigIndex <= 154) {  // 80 ms SR period
            if ((10 * (frameP & 7) + subframeP) != (SRconfig->choice.setup.sr_ConfigIndex - 75)) continue;
          } else if (SRconfig->choice.setup.sr_ConfigIndex <= 156) { // 2ms SR period
            if ((subframeP % 2) != (SRconfig->choice.setup.sr_ConfigIndex - 155)) continue;
          }
        }  // SRconfig->present == SchedulingRequestConfig_PR_setup)
      }  // SRconfig = UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated->schedulingRequestConfig)!=NULL)

      /* If we get here there is some PUCCH1 reception to schedule for SR */
      ul_req = &(eNB->UL_req[CC_id]);
      ul_req_body = &(ul_req->ul_config_request_body);
      skip_ue = 0;
      is_harq = 0;
      pdu_list_index = 0;

      /* Check that there is no existing UL grant for ULSCH which overrides the SR */
      for (int i = 0; i < ul_req_body->number_of_pdus; i++) {
        if (((ul_req_body->ul_config_pdu_list[i].pdu_type == NFAPI_UL_CONFIG_ULSCH_PDU_TYPE) ||
             (ul_req_body->ul_config_pdu_list[i].pdu_type == NFAPI_UL_CONFIG_ULSCH_HARQ_PDU_TYPE) ||
             (ul_req_body->ul_config_pdu_list[i].pdu_type == NFAPI_UL_CONFIG_ULSCH_CQI_RI_PDU_TYPE) ||
             (ul_req_body->ul_config_pdu_list[i].pdu_type == NFAPI_UL_CONFIG_ULSCH_CQI_HARQ_RI_PDU_TYPE)) &&
            (ul_req_body->ul_config_pdu_list[i].ulsch_pdu.ulsch_pdu_rel8.rnti == UE_info->UE_template[CC_id][UE_id].rnti)) {
          skip_ue = 1;
          pdu_list_index = i;
          break;
        }
        /* If there is already an HARQ pdu, convert to SR_HARQ */
        else if ((ul_req_body->ul_config_pdu_list[i].pdu_type == NFAPI_UL_CONFIG_UCI_HARQ_PDU_TYPE) &&
                 (ul_req_body->ul_config_pdu_list[i].uci_harq_pdu.ue_information.ue_information_rel8.rnti == UE_info->UE_template[CC_id][UE_id].rnti)) {
          is_harq = 1;
          pdu_list_index = i;
          break;
        }
      }

      /* Drop the allocation because ULSCH will handle it with BSR */
      if (skip_ue == 1) continue;

      LOG_D(MAC, "Frame %d, Subframe %d : Scheduling SR for UE %d/%x is_harq:%d \n",
            frameP,
            subframeP,
            UE_id,
            UE_info->UE_template[CC_id][UE_id].rnti,
            is_harq);

      /* Check Rel10 or Rel8 SR */
      if ((UE_info-> UE_template[CC_id][UE_id].physicalConfigDedicated->ext2) &&
          (UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated->ext2->schedulingRequestConfig_v1020) &&
          (UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated->ext2->schedulingRequestConfig_v1020)) {
        sr.sr_information_rel10.tl.tag                    = NFAPI_UL_CONFIG_REQUEST_SR_INFORMATION_REL10_TAG;
        sr.sr_information_rel10.number_of_pucch_resources = 1;
        sr.sr_information_rel10.pucch_index_p1            = *UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated->ext2->schedulingRequestConfig_v1020->sr_PUCCH_ResourceIndexP1_r10;
        LOG_D(MAC, "REL10 PUCCH INDEX P1:%d \n", sr.sr_information_rel10.pucch_index_p1);
      } else {
        sr.sr_information_rel8.tl.tag      = NFAPI_UL_CONFIG_REQUEST_SR_INFORMATION_REL8_TAG;
        sr.sr_information_rel8.pucch_index = UE_info->UE_template[CC_id][UE_id].physicalConfigDedicated->schedulingRequestConfig->choice.setup.sr_PUCCH_ResourceIndex;
        LOG_D(MAC, "REL8 PUCCH INDEX:%d\n", sr.sr_information_rel8.pucch_index);
      }

      /* If there is already an HARQ pdu, convert to SR_HARQ */
      if (is_harq) {
        nfapi_ul_config_harq_information harq                                            = ul_req_body->ul_config_pdu_list[pdu_list_index].uci_harq_pdu.harq_information;
        ul_req_body->ul_config_pdu_list[pdu_list_index].pdu_type                         = NFAPI_UL_CONFIG_UCI_SR_HARQ_PDU_TYPE;
        ul_req_body->ul_config_pdu_list[pdu_list_index].uci_sr_harq_pdu.sr_information   = sr;
        ul_req_body->ul_config_pdu_list[pdu_list_index].uci_sr_harq_pdu.harq_information = harq;
      } else {
        ul_req_body->ul_config_pdu_list[ul_req_body->number_of_pdus].pdu_type                                              = NFAPI_UL_CONFIG_UCI_SR_PDU_TYPE;
        ul_req_body->ul_config_pdu_list[ul_req_body->number_of_pdus].uci_sr_pdu.ue_information.ue_information_rel8.tl.tag  = NFAPI_UL_CONFIG_REQUEST_UE_INFORMATION_REL8_TAG;
        ul_req_body->ul_config_pdu_list[ul_req_body->number_of_pdus].uci_sr_pdu.ue_information.ue_information_rel8.rnti    = UE_info->UE_template[CC_id][UE_id].rnti;
        ul_req_body->ul_config_pdu_list[ul_req_body->number_of_pdus].uci_sr_pdu.ue_information.ue_information_rel11.tl.tag = 0;
        ul_req_body->ul_config_pdu_list[ul_req_body->number_of_pdus].uci_sr_pdu.ue_information.ue_information_rel13.tl.tag = 0;
        ul_req_body->ul_config_pdu_list[ul_req_body->number_of_pdus].uci_sr_pdu.sr_information                             = sr;
        ul_req_body->number_of_pdus++;
      }  // if (is_harq)

      ul_req_body->tl.tag = NFAPI_UL_CONFIG_REQUEST_BODY_TAG;
    }  // for (int UE_id = 0; UE_id < MAX_MOBILES_PER_ENB; UE_id++)
  }  // for (CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++)
}

void
check_ul_failure(module_id_t module_idP, int CC_id, int UE_id,
                 frame_t frameP, sub_frame_t subframeP) {
  UE_info_t                 *UE_info = &RC.mac[module_idP]->UE_info;
  nfapi_dl_config_request_t  *DL_req = &RC.mac[module_idP]->DL_req[0];
  uint16_t                      rnti = UE_RNTI(module_idP, UE_id);
  COMMON_channels_t              *cc = RC.mac[module_idP]->common_channels;

  // check uplink failure
  if ((UE_info->UE_sched_ctrl[UE_id].ul_failure_timer > 0) &&
      (UE_info->UE_sched_ctrl[UE_id].ul_out_of_sync == 0)) {
    if (UE_info->UE_sched_ctrl[UE_id].ul_failure_timer == 1)
      LOG_I(MAC, "UE %d rnti %x: UL Failure timer %d \n", UE_id, rnti,
            UE_info->UE_sched_ctrl[UE_id].ul_failure_timer);

    if (UE_info->UE_sched_ctrl[UE_id].ra_pdcch_order_sent == 0) {
      UE_info->UE_sched_ctrl[UE_id].ra_pdcch_order_sent = 1;
      // add a format 1A dci for this UE to request an RA procedure (only one UE per subframe)
      nfapi_dl_config_request_pdu_t *dl_config_pdu                    = &DL_req[CC_id].dl_config_request_body.dl_config_pdu_list[DL_req[CC_id].dl_config_request_body.number_pdu];
      memset((void *) dl_config_pdu, 0,sizeof(nfapi_dl_config_request_pdu_t));
      dl_config_pdu->pdu_type                                         = NFAPI_DL_CONFIG_DCI_DL_PDU_TYPE;
      dl_config_pdu->pdu_size                                         = (uint8_t) (2 + sizeof(nfapi_dl_config_dci_dl_pdu));
      dl_config_pdu->dci_dl_pdu.dci_dl_pdu_rel8.tl.tag                = NFAPI_DL_CONFIG_REQUEST_DCI_DL_PDU_REL8_TAG;
      dl_config_pdu->dci_dl_pdu.dci_dl_pdu_rel8.dci_format            = NFAPI_DL_DCI_FORMAT_1A;
      dl_config_pdu->dci_dl_pdu.dci_dl_pdu_rel8.aggregation_level     = get_aggregation(get_bw_index(module_idP, CC_id),
          UE_info->UE_sched_ctrl[UE_id].
          dl_cqi[CC_id], format1A);
      dl_config_pdu->dci_dl_pdu.dci_dl_pdu_rel8.rnti                  = rnti;
      dl_config_pdu->dci_dl_pdu.dci_dl_pdu_rel8.rnti_type             = 1;  // CRNTI : see Table 4-10 from SCF082 - nFAPI specifications
      dl_config_pdu->dci_dl_pdu.dci_dl_pdu_rel8.transmission_power    = 6000; // equal to RS power
      AssertFatal((cc[CC_id].mib->message.dl_Bandwidth >= 0) && (cc[CC_id].mib->message.dl_Bandwidth < 6),
                  "illegal dl_Bandwidth %d\n",
                  (int) cc[CC_id].mib->message.dl_Bandwidth);
      dl_config_pdu->dci_dl_pdu.dci_dl_pdu_rel8.resource_block_coding = pdcch_order_table[cc[CC_id].mib->message.dl_Bandwidth];
      DL_req[CC_id].dl_config_request_body.number_dci++;
      DL_req[CC_id].dl_config_request_body.number_pdu++;
      DL_req[CC_id].dl_config_request_body.tl.tag                      = NFAPI_DL_CONFIG_REQUEST_BODY_TAG;
      DL_req[CC_id].sfn_sf = frameP<<4 | subframeP;
      LOG_D(MAC,
            "UE %d rnti %x: sending PDCCH order for RAPROC (failure timer %d), resource_block_coding %d \n",
            UE_id, rnti,
            UE_info->UE_sched_ctrl[UE_id].ul_failure_timer,
            dl_config_pdu->dci_dl_pdu.
            dci_dl_pdu_rel8.resource_block_coding);
    } else {    // ra_pdcch_sent==1
      LOG_D(MAC,
            "UE %d rnti %x: sent PDCCH order for RAPROC waiting (failure timer %d) \n",
            UE_id, rnti,
            UE_info->UE_sched_ctrl[UE_id].ul_failure_timer);

      if ((UE_info->UE_sched_ctrl[UE_id].ul_failure_timer % 80) == 0) UE_info->UE_sched_ctrl[UE_id].ra_pdcch_order_sent = 0;  // resend every 8 frames
    }

    UE_info->UE_sched_ctrl[UE_id].ul_failure_timer++;

    // check threshold
    if (UE_info->UE_sched_ctrl[UE_id].ul_failure_timer > 4000) {
      // note: probably ul_failure_timer should be less than UE radio link failure time(see T310/N310/N311)
      if (NODE_IS_DU(RC.rrc[module_idP]->node_type)) {
        MessageDef *m = itti_alloc_new_message(TASK_MAC_ENB, 0, F1AP_UE_CONTEXT_RELEASE_REQ);
        F1AP_UE_CONTEXT_RELEASE_REQ(m).rnti = rnti;
        F1AP_UE_CONTEXT_RELEASE_REQ(m).cause = F1AP_CAUSE_RADIO_NETWORK;
        F1AP_UE_CONTEXT_RELEASE_REQ(m).cause_value = 1; // 1 = F1AP_CauseRadioNetwork_rl_failure
        F1AP_UE_CONTEXT_RELEASE_REQ(m).rrc_container = NULL;
        F1AP_UE_CONTEXT_RELEASE_REQ(m).rrc_container_length = 0;
        itti_send_msg_to_task(TASK_DU_F1, module_idP, m);
      } else {
        // inform RRC of failure and clear timer
        LOG_I(MAC, "UE %d rnti %x: UL Failure after repeated PDCCH orders: Triggering RRC \n",
              UE_id,
              rnti);
        mac_eNB_rrc_ul_failure(module_idP,
                               CC_id,
                               frameP,
                               subframeP,
                               rnti);
      }

      UE_info->UE_sched_ctrl[UE_id].ul_failure_timer = 0;
      UE_info->UE_sched_ctrl[UE_id].ul_out_of_sync   = 1;
    }
  }       // ul_failure_timer>0

  UE_info->UE_sched_ctrl[UE_id].uplane_inactivity_timer++;

  if((U_PLANE_INACTIVITY_VALUE != 0) && (UE_info->UE_sched_ctrl[UE_id].uplane_inactivity_timer > (U_PLANE_INACTIVITY_VALUE * 10))) {
    LOG_D(MAC,"UE %d rnti %x: U-Plane Failure after repeated PDCCH orders: Triggering RRC \n",UE_id,rnti);
    mac_eNB_rrc_uplane_failure(module_idP,CC_id,frameP,subframeP,rnti);
    UE_info->UE_sched_ctrl[UE_id].uplane_inactivity_timer  = 0;
  }// time > 60s
}

void
clear_nfapi_information(eNB_MAC_INST *eNB, int CC_idP,
                        frame_t frameP, sub_frame_t subframeP) {
  nfapi_dl_config_request_t      *DL_req = &eNB->DL_req[0];
  nfapi_ul_config_request_t      *UL_req = &eNB->UL_req[0];
  nfapi_hi_dci0_request_t   *HI_DCI0_req = &eNB->HI_DCI0_req[CC_idP][subframeP];
  nfapi_tx_request_t             *TX_req = &eNB->TX_req[0];
  eNB->pdu_index[CC_idP] = 0;

  if (NFAPI_MODE == NFAPI_MODE_PNF || NFAPI_MODE == NFAPI_MONOLITHIC) { // monolithic or PNF
    DL_req[CC_idP].dl_config_request_body.number_pdcch_ofdm_symbols           = 1;
    DL_req[CC_idP].dl_config_request_body.number_dci                          = 0;
    DL_req[CC_idP].dl_config_request_body.number_pdu                          = 0;
    DL_req[CC_idP].dl_config_request_body.number_pdsch_rnti                   = 0;
    DL_req[CC_idP].dl_config_request_body.transmission_power_pcfich           = 6000;
    DL_req[CC_idP].sfn_sf                                                     = subframeP + (frameP<<4);
    HI_DCI0_req->hi_dci0_request_body.sfnsf                                   = subframeP + (frameP<<4);
    HI_DCI0_req->hi_dci0_request_body.number_of_dci                           = 0;
    UL_req[CC_idP].ul_config_request_body.number_of_pdus                      = 0;
    UL_req[CC_idP].ul_config_request_body.rach_prach_frequency_resources      = 0; // ignored, handled by PHY for now
    UL_req[CC_idP].ul_config_request_body.srs_present                         = 0; // ignored, handled by PHY for now
    TX_req[CC_idP].tx_request_body.number_of_pdus                 = 0;
  }
}

void
copy_ulreq(module_id_t module_idP, frame_t frameP, sub_frame_t subframeP) {
  int CC_id;
  eNB_MAC_INST *mac = RC.mac[module_idP];

  for (CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) {
    nfapi_ul_config_request_t *ul_req_tmp             = &mac->UL_req_tmp[CC_id][subframeP];
    nfapi_ul_config_request_t *ul_req                 = &mac->UL_req[CC_id];
    nfapi_ul_config_request_pdu_t *ul_req_pdu         = ul_req->ul_config_request_body.ul_config_pdu_list;
    *ul_req = *ul_req_tmp;
    // Restore the pointer
    ul_req->ul_config_request_body.ul_config_pdu_list = ul_req_pdu;
    ul_req->sfn_sf                                    = (frameP<<4) + subframeP;
    ul_req_tmp->ul_config_request_body.number_of_pdus = 0;

    if (ul_req->ul_config_request_body.number_of_pdus>0) {
      LOG_D(MAC, "%s() active NOW (frameP:%d subframeP:%d) pdus:%d\n", __FUNCTION__, frameP, subframeP, ul_req->ul_config_request_body.number_of_pdus);
    }

    memcpy((void *)ul_req->ul_config_request_body.ul_config_pdu_list,
           (void *)ul_req_tmp->ul_config_request_body.ul_config_pdu_list,
           ul_req->ul_config_request_body.number_of_pdus*sizeof(nfapi_ul_config_request_pdu_t));
  }
}

#include <openair1/PHY/LTE_TRANSPORT/transport_proto.h>

void
eNB_dlsch_ulsch_scheduler(module_id_t module_idP,
                          frame_t frameP,
                          sub_frame_t subframeP) {
  int               mbsfn_status[MAX_NUM_CCs];
  protocol_ctxt_t   ctxt;
  rnti_t            rnti  = 0;
  int               CC_id = 0;
  int               UE_id = -1;
  eNB_MAC_INST      *eNB                    = RC.mac[module_idP];
  UE_info_t         *UE_info                = &(eNB->UE_info);
  COMMON_channels_t *cc                     = eNB->common_channels;
  UE_sched_ctrl_t     *UE_scheduling_control  = NULL;
  start_meas(&(eNB->eNB_scheduler));
  VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_ENB_DLSCH_ULSCH_SCHEDULER, VCD_FUNCTION_IN);
  // TODO: Better solution needed this is the first
  // 3 indications of this function on startup
  // 1303275.278188 [MAC]   XXX 0.0 -> 0.4 = 4
  // 1303275.279443 [MAC]   XXX 0.4 -> 639.5 = 6391
  // 1303275.348686 [MAC]   XXX 646.3 -> 646.3 = 0
  int delta = (frameP * 10 + subframeP) - (eNB->frame * 10 + eNB->subframe);
  if (delta < 0)
  {
    delta += 10240; // sfn_sf decimal values range from 0 to 10239
  }
  // If we ever see a difference this big something is very wrong
  // This threshold is arbitrary
  if (delta > 8500 || delta == 0) // 850 frames
  {
    LOG_I(MAC, "scheduler ignoring outerspace %d.%d -> %d.%d = %d\n",
          eNB->frame, eNB->subframe, frameP, subframeP, delta);
    return;
  }
  LOG_D(MAC, "Entering dlsch_ulsch scheduler %d.%d -> %d.%d = %d\n",
        eNB->frame, eNB->subframe, frameP, subframeP, delta);

  eNB->frame    = frameP;
  eNB->subframe = subframeP;

  for (CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) {
    mbsfn_status[CC_id] = 0;
    /* Clear vrb_maps */
    memset(cc[CC_id].vrb_map, 0, 100);
    memset(cc[CC_id].vrb_map_UL, 0, 100);
    cc[CC_id].mcch_active = 0;
    clear_nfapi_information(RC.mac[module_idP], CC_id, frameP, subframeP);

    /* hack: skip BCH RBs in subframe 0 for DL scheduling,
     *       because with high MCS we may exceed code rate 0.93
     *       when using those RBs (36.213 7.1.7 says the UE may
     *       skip decoding if the code rate is higher than 0.93)
     * TODO: remove this hack, deal with physical bits properly
     *       i.e. reduce MCS in the scheduler if code rate > 0.93
     */
    if (subframeP == 0) {
      int i;
      int bw = cc[CC_id].mib->message.dl_Bandwidth;
      /* start and count defined for RBs: 6, 15, 25, 50, 75, 100 */
      int start[6] = { 0, 4, 9, 22, 34, 47 };
      int count[6] = { 6, 7, 7,  6,  7,  6 };
      for (i = 0; i < count[bw]; i++)
        cc[CC_id].vrb_map[start[bw] + i] = 1;
    }
  }

  /* Refresh UE list based on UEs dropped by PHY in previous subframe */
  for (UE_id = 0; UE_id < MAX_MOBILES_PER_ENB; UE_id++) {
    if (UE_info->active[UE_id]) {
      rnti = UE_RNTI(module_idP, UE_id);
      CC_id = UE_PCCID(module_idP, UE_id);
      UE_scheduling_control = &(UE_info->UE_sched_ctrl[UE_id]);

/* to be merged with MAC_stats.log generation. probably redundant
      if (((frameP & 127) == 0) && (subframeP == 0)) {
        double total_bler;
        if(UE_scheduling_control->pusch_rx_num[CC_id] == 0 && UE_scheduling_control->pusch_rx_error_num[CC_id] == 0) {
          total_bler = 0;
        }
        else {
          total_bler = (double)UE_scheduling_control->pusch_rx_error_num[CC_id] / (double)(UE_scheduling_control->pusch_rx_error_num[CC_id] + UE_scheduling_control->pusch_rx_num[CC_id]) * 100;
        }
        LOG_I(MAC,"UE %x : %s, PHR %d DLCQI %d PUSCH %d PUCCH %d RLC disc %d UL-stat rcv %lu err %lu bler %lf mcsoff %d bsr %u sched %u tbs %lu cnt %u , DL-stat tbs %lu cnt %u rb %u buf %u 1st %u ret %u ri %d\n",
              rnti,
              UE_scheduling_control->ul_out_of_sync == 0 ? "in synch" : "out of sync",
              UE_info->UE_template[CC_id][UE_id].phr_info,
              UE_scheduling_control->dl_cqi[CC_id],
              UE_scheduling_control->pusch_snr_avg[CC_id],
              UE_scheduling_control->pucch1_snr[CC_id],
              UE_scheduling_control->rlc_out_of_resources_cnt,
              UE_scheduling_control->pusch_rx_num[CC_id],
              UE_scheduling_control->pusch_rx_error_num[CC_id],
              total_bler,
              UE_scheduling_control->mcs_offset[CC_id],
              UE_info->UE_template[CC_id][UE_id].estimated_ul_buffer,
              UE_info->UE_template[CC_id][UE_id].scheduled_ul_bytes,
              UE_info->eNB_UE_stats[CC_id][UE_id].total_pdu_bytes_rx,
              UE_info->eNB_UE_stats[CC_id][UE_id].total_num_pdus_rx,
              UE_info->eNB_UE_stats[CC_id][UE_id].total_pdu_bytes,
              UE_info->eNB_UE_stats[CC_id][UE_id].total_num_pdus,
              UE_info->eNB_UE_stats[CC_id][UE_id].total_rbs_used,
#if defined(PRE_SCD_THREAD)
              UE_info->UE_template[CC_id][UE_id].dl_buffer_total,
#else
              0,
#endif
              UE_scheduling_control->first_cnt[CC_id],
              UE_scheduling_control->ret_cnt[CC_id],
              UE_scheduling_control->aperiodic_ri_received[CC_id]
        );
      }
*/
      RC.eNB[module_idP][CC_id]->pusch_stats_bsr[UE_id][(frameP * 10) + subframeP] = -63;

      if (UE_id == UE_info->list.head) {
        VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_UE0_BSR, RC.eNB[module_idP][CC_id]->pusch_stats_bsr[UE_id][(frameP * 10) + subframeP]);
      }

      /* Set and increment CDRX related timers */
      if (UE_scheduling_control->cdrx_configured == true) {
        bool harq_active_time_condition = false;
        UE_TEMPLATE *UE_template = NULL;
        unsigned long active_time_condition = 0; // variable used only for tracing purpose

        /* (UL and DL) HARQ RTT timers and DRX retransmission timers */
        for (int harq_process_id = 0; harq_process_id < 8; harq_process_id++) {
          /* DL asynchronous HARQ process */
          if (UE_scheduling_control->drx_retransmission_timer[harq_process_id] > 0) {
            UE_scheduling_control->drx_retransmission_timer[harq_process_id]++;

            if (UE_scheduling_control->drx_retransmission_timer[harq_process_id] > UE_scheduling_control->drx_retransmission_timer_thres[harq_process_id]) {
              UE_scheduling_control->drx_retransmission_timer[harq_process_id] = 0;
            }
          }

          if (UE_scheduling_control->harq_rtt_timer[CC_id][harq_process_id] > 0) {
            UE_scheduling_control->harq_rtt_timer[CC_id][harq_process_id]++;

            if (UE_scheduling_control->harq_rtt_timer[CC_id][harq_process_id] > 8) {
              /* Note: here drx_retransmission_timer is restarted instead of started in the specification */
              UE_scheduling_control->drx_retransmission_timer[harq_process_id] = 1; // started when HARQ RTT timer expires
              UE_scheduling_control->harq_rtt_timer[CC_id][harq_process_id] = 0;
            }
          }

          /* UL asynchronous HARQ process: only UL HARQ RTT timer is implemented (hence not implemented) */
          if (UE_scheduling_control->ul_harq_rtt_timer[CC_id][harq_process_id] > 0) {
            UE_scheduling_control->ul_harq_rtt_timer[CC_id][harq_process_id]++;

            if (UE_scheduling_control->ul_harq_rtt_timer[CC_id][harq_process_id] > 4) {
              /*
               * TODO: implement the handling of UL asynchronous HARQ
               * drx_ULRetransmissionTimer should be (re)started here
               */
              UE_scheduling_control->ul_harq_rtt_timer[CC_id][harq_process_id] = 0;
            }
          }

          /* UL synchronous HARQ process */
          if (UE_scheduling_control->ul_synchronous_harq_timer[CC_id][harq_process_id] > 0) {
            UE_scheduling_control->ul_synchronous_harq_timer[CC_id][harq_process_id]++;

            if (UE_scheduling_control->ul_synchronous_harq_timer[CC_id][harq_process_id] > 5) {
              harq_active_time_condition = true;
              UE_scheduling_control->ul_synchronous_harq_timer[CC_id][harq_process_id] = 0;
              active_time_condition = 5; // for tracing purpose
            }
          }
        }

        /* On duration timer */
        if (UE_scheduling_control->on_duration_timer > 0) {
          UE_scheduling_control->on_duration_timer++;

          if (UE_scheduling_control->on_duration_timer > UE_scheduling_control->on_duration_timer_thres) {
            UE_scheduling_control->on_duration_timer = 0;
          }
        }

        /* DRX inactivity timer */
        if (UE_scheduling_control->drx_inactivity_timer > 0) {
          UE_scheduling_control->drx_inactivity_timer++;

          if (UE_scheduling_control->drx_inactivity_timer > (UE_scheduling_control->drx_inactivity_timer_thres + 1)) {
            /* Note: the +1 on the threshold is due to information in table C-1 of 36.321 */
            UE_scheduling_control->drx_inactivity_timer = 0;

            /* When timer expires switch into short or long DRX cycle */
            if (UE_scheduling_control->drx_shortCycle_timer_thres > 0) {
              UE_scheduling_control->in_short_drx_cycle = true;
              UE_scheduling_control->drx_shortCycle_timer = 0;
              UE_scheduling_control->in_long_drx_cycle = false;
            } else {
              UE_scheduling_control->in_long_drx_cycle = true;
            }
          }
        }

        /* Short DRX Cycle */
        if (UE_scheduling_control->in_short_drx_cycle == true) {
          UE_scheduling_control->drx_shortCycle_timer++;

          /* When the Short DRX cycles are over, switch to long DRX cycle */
          if (UE_scheduling_control->drx_shortCycle_timer > UE_scheduling_control->drx_shortCycle_timer_thres) {
            UE_scheduling_control->drx_shortCycle_timer = 0;
            UE_scheduling_control->in_short_drx_cycle = false;
            UE_scheduling_control->in_long_drx_cycle = true;
            UE_scheduling_control->drx_longCycle_timer = 0;
          }
        } else {
          UE_scheduling_control->drx_shortCycle_timer = 0;
        }

        /* Long DRX Cycle */
        if (UE_scheduling_control->in_long_drx_cycle == true) {
          UE_scheduling_control->drx_longCycle_timer++;

          if (UE_scheduling_control->drx_longCycle_timer > UE_scheduling_control->drx_longCycle_timer_thres) {
            UE_scheduling_control->drx_longCycle_timer = 1;
          }
        } else {
          UE_scheduling_control->drx_longCycle_timer = 0;
        }

        /* Check for error cases */
        if ((UE_scheduling_control->in_short_drx_cycle == true) && (UE_scheduling_control->in_long_drx_cycle == true)) {
          LOG_E(MAC, "Error in C-DRX: UE id %d is in both short and long DRX cycle. Should not happen. Back it to long cycle only\n", UE_id);
          UE_scheduling_control->in_short_drx_cycle = false;
        }

        /* Condition to start On Duration Timer */
        if (UE_scheduling_control->in_short_drx_cycle == true && UE_scheduling_control->on_duration_timer == 0) {
          if (((frameP * 10) + subframeP) % (UE_scheduling_control->short_drx_cycle_duration) ==
              (UE_scheduling_control->drx_start_offset) % (UE_scheduling_control->short_drx_cycle_duration)) {
            UE_scheduling_control->on_duration_timer = 1;
          }
        } else if (UE_scheduling_control->in_long_drx_cycle == true && UE_scheduling_control->on_duration_timer == 0) {
          if (((frameP * 10) + subframeP) % (UE_scheduling_control->drx_longCycle_timer_thres) ==
              (UE_scheduling_control->drx_start_offset)) {
            UE_scheduling_control->on_duration_timer = 1;
          }
        }

        /* Update Active Time status of UE
         * Based on 36.321 5.7 the differents conditions for the UE to be in Acttive Should be check ONLY
         * here for the current subframe. The variable 'UE_scheduling_control->in_active_time' should be updated
         * ONLY here. The variable can then be used for testing the actual state of the UE for scheduling purpose.
         */
        UE_template = &(UE_info->UE_template[CC_id][UE_id]);

        /* (a)synchronous HARQ processes handling for Active Time */
        for (int harq_process_id = 0; harq_process_id < 8; harq_process_id++) {
          if (UE_scheduling_control->drx_retransmission_timer[harq_process_id] > 0) {
            harq_active_time_condition = true;
            active_time_condition = 2; // for tracing purpose
            break;
          }
        }

        /* Active time conditions */
        if (UE_scheduling_control->on_duration_timer > 0 ||
            UE_scheduling_control->drx_inactivity_timer > 1 ||
            harq_active_time_condition ||
            UE_template->ul_SR > 0) {
          UE_scheduling_control->in_active_time = true;
        } else {
          UE_scheduling_control->in_active_time = false;
        }

        /* BEGIN VCD */
        if (UE_id == 0) {
          VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_ON_DURATION_TIMER, (unsigned long) UE_scheduling_control->on_duration_timer);
          VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_DRX_INACTIVITY, (unsigned long) UE_scheduling_control->drx_inactivity_timer);
          VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_DRX_SHORT_CYCLE, (unsigned long) UE_scheduling_control->drx_shortCycle_timer);
          VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_DRX_LONG_CYCLE, (unsigned long) UE_scheduling_control->drx_longCycle_timer);
          VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_DRX_RETRANSMISSION_HARQ0, (unsigned long) UE_scheduling_control->drx_retransmission_timer[0]);
          VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_DRX_ACTIVE_TIME, (unsigned long) UE_scheduling_control->in_active_time);

          /* For tracing purpose */
          if (UE_template->ul_SR > 0) {
            active_time_condition = 1;
          } else if ((UE_scheduling_control->on_duration_timer > 0) && (active_time_condition == 0)) {
            active_time_condition = 3;
          } else if ((UE_scheduling_control->drx_inactivity_timer > 1) && (active_time_condition == 0)) {
            active_time_condition = 4;
          }

          VCD_SIGNAL_DUMPER_DUMP_VARIABLE_BY_NAME(VCD_SIGNAL_DUMPER_VARIABLES_DRX_ACTIVE_TIME_CONDITION, (unsigned long) active_time_condition);
        }

        /* END VCD */

        /* DCI0 ongoing timer */
        if (UE_scheduling_control->dci0_ongoing_timer > 0) {
          if (UE_scheduling_control->dci0_ongoing_timer > 7) {
            UE_scheduling_control->dci0_ongoing_timer = 0;
          } else {
            UE_scheduling_control->dci0_ongoing_timer++;
          }
        }
      } else { // else: CDRX not configured
        /* Note: (UL) HARQ RTT timers processing is done here and can be used by other features than CDRX */
        /* HARQ RTT timers */
        for (int harq_process_id = 0; harq_process_id < 8; harq_process_id++) {
          if (UE_scheduling_control->harq_rtt_timer[CC_id][harq_process_id] > 0) {
            UE_scheduling_control->harq_rtt_timer[CC_id][harq_process_id]++;

            if (UE_scheduling_control->harq_rtt_timer[CC_id][harq_process_id] > 8) {
              UE_scheduling_control->harq_rtt_timer[CC_id][harq_process_id] = 0;
            }
          }

          if (UE_scheduling_control->ul_harq_rtt_timer[CC_id][harq_process_id] > 0) {
            UE_scheduling_control->ul_harq_rtt_timer[CC_id][harq_process_id]++;

            if (UE_scheduling_control->ul_harq_rtt_timer[CC_id][harq_process_id] > 4) {
              UE_scheduling_control->ul_harq_rtt_timer[CC_id][harq_process_id] = 0;
            }
          }
        } // end loop harq process
      } // end else CDRX not configured

      /* Increment these timers, they are cleared when we receive an sdu */
      UE_scheduling_control->ul_inactivity_timer++;
      UE_scheduling_control->cqi_req_timer++;
      LOG_D(MAC, "UE %d/%x : ul_inactivity %d, cqi_req %d\n",
            UE_id,
            rnti,
            UE_scheduling_control->ul_inactivity_timer,
            UE_scheduling_control->cqi_req_timer);
      check_ul_failure(module_idP, CC_id, UE_id, frameP, subframeP);

      if (UE_scheduling_control->ue_reestablishment_reject_timer > 0) {
        UE_scheduling_control->ue_reestablishment_reject_timer++;

        if (UE_scheduling_control->ue_reestablishment_reject_timer >= UE_scheduling_control->ue_reestablishment_reject_timer_thres) {
          UE_scheduling_control->ue_reestablishment_reject_timer = 0;

          /* Clear reestablish_rnti_map */
          if (UE_scheduling_control->ue_reestablishment_reject_timer_thres > 20) {
            for (int ue_id_l = 0; ue_id_l < MAX_MOBILES_PER_ENB; ue_id_l++) {
              if (reestablish_rnti_map[ue_id_l][0] == rnti) {
                /* Clear currentC-RNTI from map */
                reestablish_rnti_map[ue_id_l][0] = 0;
                reestablish_rnti_map[ue_id_l][1] = 0;
                break;
              }
            }

            PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, module_idP, ENB_FLAG_YES, rnti, 0, 0,module_idP);
            rrc_rlc_remove_ue(&ctxt);
            pdcp_remove_UE(&ctxt);
          }

          /* Note: This should not be done in the MAC! */
	  /*
          for (int ii=0; ii<MAX_MOBILES_PER_ENB; ii++) {
            LTE_eNB_ULSCH_t *ulsch = RC.eNB[module_idP][CC_id]->ulsch[ii];

            if((ulsch != NULL) && (ulsch->rnti == rnti)) {
              void clean_eNb_ulsch(LTE_eNB_ULSCH_t *ulsch);
              LOG_I(MAC, "clean_eNb_ulsch UE %x \n", rnti);
              clean_eNb_ulsch(ulsch);
            }
          }

          for (int ii=0; ii<MAX_MOBILES_PER_ENB; ii++) {
            LTE_eNB_DLSCH_t *dlsch = RC.eNB[module_idP][CC_id]->dlsch[ii][0];

            if((dlsch != NULL) && (dlsch->rnti == rnti)) {
              void clean_eNb_dlsch(LTE_eNB_DLSCH_t *dlsch);
              LOG_I(MAC, "clean_eNb_dlsch UE %x \n", rnti);
              clean_eNb_dlsch(dlsch);
            }
          }
	  */

	int id;

	// clean ULSCH entries for rnti
	id = find_ulsch(rnti,RC.eNB[module_idP][CC_id],SEARCH_EXIST);
        if (id>=0) clean_eNb_ulsch(RC.eNB[module_idP][CC_id]->ulsch[id]);

	// clean DLSCH entries for rnti
	id = find_dlsch(rnti,RC.eNB[module_idP][CC_id],SEARCH_EXIST);
        if (id>=0) clean_eNb_dlsch(RC.eNB[module_idP][CC_id]->dlsch[id][0]);

          for (int j = 0; j < 10; j++) {
            nfapi_ul_config_request_body_t *ul_req_tmp = NULL;
            ul_req_tmp = &(eNB->UL_req_tmp[CC_id][j].ul_config_request_body);

            if (ul_req_tmp) {
              int pdu_number = ul_req_tmp->number_of_pdus;

              for (int pdu_index = pdu_number-1; pdu_index >= 0; pdu_index--) {
                if (ul_req_tmp->ul_config_pdu_list[pdu_index].ulsch_pdu.ulsch_pdu_rel8.rnti == rnti) {
                  LOG_I(MAC, "remove UE %x from ul_config_pdu_list %d/%d\n",
                        rnti,
                        pdu_index,
                        pdu_number);

                  if (pdu_index < pdu_number -1) {
                    memcpy(&ul_req_tmp->ul_config_pdu_list[pdu_index],
                           &ul_req_tmp->ul_config_pdu_list[pdu_index+1],
                           (pdu_number-1-pdu_index) * sizeof(nfapi_ul_config_request_pdu_t));
                  }

                  ul_req_tmp->number_of_pdus--;
                }
              } // end for pdu_index
            } // end if (ul_req_tmp)
          } // end for j

          rrc_mac_remove_ue(module_idP,rnti);
        } // end if (UE_scheduling_control->ue_reestablishment_reject_timer >= UE_scheduling_control->ue_reestablishment_reject_timer_thres)
      } // end if (UE_scheduling_control->ue_reestablishment_reject_timer > 0)
    } // end if UE active
  } // end for loop on UE_id

#if (!defined(PRE_SCD_THREAD))
  if (!NODE_IS_DU(RC.rrc[module_idP]->node_type)) {
    void rlc_tick(int, int);
    PROTOCOL_CTXT_SET_BY_MODULE_ID(&ctxt, module_idP, ENB_FLAG_YES, NOT_A_RNTI, frameP, subframeP, module_idP);
    rlc_tick(frameP, subframeP);
    pdcp_run(&ctxt);
    pdcp_mbms_run(&ctxt);
    rrc_rx_tx(&ctxt, CC_id);
  }
#endif

  int do_fembms_si=0;
  for (CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) {
    if (cc[CC_id].MBMS_flag > 0) {
      start_meas(&RC.mac[module_idP]->schedule_mch);
      int(*schedule_mch)(module_id_t module_idP, uint8_t CC_id, frame_t frameP, sub_frame_t subframe) = NULL;
      schedule_mch = schedule_MBMS_NFAPI;
      if(schedule_mch){
      	mbsfn_status[CC_id] = schedule_mch(module_idP, CC_id, frameP, subframeP);
      }
      stop_meas(&RC.mac[module_idP]->schedule_mch);
    }
    if (cc[CC_id].FeMBMS_flag > 0) {
	do_fembms_si = 1;
    }

  }

  static int debug_flag = 0;
  void (*schedule_ulsch_p)(module_id_t module_idP, frame_t frameP, sub_frame_t subframe) = NULL;
  void (*schedule_ue_spec_p)(module_id_t module_idP, frame_t frameP, sub_frame_t subframe, int *mbsfn_flag) = NULL;

  if (eNB->scheduler_mode == SCHED_MODE_DEFAULT) {
    schedule_ulsch_p = schedule_ulsch;
    schedule_ue_spec_p = schedule_dlsch;
  } else if (eNB->scheduler_mode == SCHED_MODE_FAIR_RR) {
    memset(dlsch_ue_select, 0, sizeof(dlsch_ue_select));
    schedule_ulsch_p = schedule_ulsch_fairRR;
    schedule_ue_spec_p = schedule_ue_spec_fairRR;
  }

  if(debug_flag == 0) {
    LOG_E(MAC,"SCHED_MODE = %d\n", eNB->scheduler_mode);
    debug_flag = 1;
  }

  /* This schedules MIB */
  if(!do_fembms_si/*get_softmodem_params()->fembms*/){
    if ((subframeP == 0) && (frameP & 3) == 0)
      schedule_mib(module_idP, frameP, subframeP);
  }else{
    if ((subframeP == 0) && (frameP & 15) == 0 ){
       schedule_fembms_mib(module_idP, frameP, subframeP);
       //schedule_SI_MBMS(module_idP, frameP, subframeP);
    }
  }

  if (get_softmodem_params()->phy_test == 0) {
    /* This schedules SI for legacy LTE and eMTC starting in subframeP */
    if(!do_fembms_si/*get_softmodem_params()->fembms*/)
       schedule_SI(module_idP, frameP, subframeP);
    else
       schedule_SI_MBMS(module_idP, frameP, subframeP);
    /* This schedules Paging in subframeP */
    schedule_PCH(module_idP,frameP,subframeP);
    /* This schedules Random-Access for legacy LTE and eMTC starting in subframeP */
    schedule_RA(module_idP, frameP, subframeP);
    /* Copy previously scheduled UL resources (ULSCH + HARQ) */
    copy_ulreq(module_idP, frameP, subframeP);
    /* This schedules SRS in subframeP */
    schedule_SRS(module_idP, frameP, subframeP);

    /* This schedules ULSCH in subframeP (dci0) */
    if (schedule_ulsch_p != NULL) {
      schedule_ulsch_p(module_idP, frameP, subframeP);
    } else {
      LOG_E(MAC," %s %d: schedule_ulsch_p is NULL, function not called\n",
            __FILE__,
            __LINE__);
    }

    /* This schedules UCI_SR in subframeP */
    schedule_SR(module_idP, frameP, subframeP);
    /* This schedules UCI_CSI in subframeP */
    schedule_CSI(module_idP, frameP, subframeP);
    /* This schedules DLSCH in subframeP for BR UE*/
    schedule_ue_spec_br(module_idP, frameP, subframeP);

    /* This schedules DLSCH in subframeP */
    if (schedule_ue_spec_p != NULL) {
      schedule_ue_spec_p(module_idP, frameP, subframeP, mbsfn_status);
    } else {
      LOG_E(MAC," %s %d: schedule_ue_spec_p is NULL, function not called\n",
            __FILE__,
            __LINE__);
    }
  } else {
    schedule_ulsch_phy_test(module_idP,frameP,subframeP);
    schedule_ue_spec_phy_test(module_idP,frameP,subframeP,mbsfn_status);
  }

  /* Allocate CCEs for good after scheduling is done */
  for (CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) {
    if (cc[CC_id].tdd_Config == NULL || !(is_UL_sf(&cc[CC_id],subframeP))) {
      int rc = allocate_CCEs(module_idP, CC_id, frameP, subframeP, 2);
      if (rc < 0)
        LOG_E(MAC, "%s() %4d.%d ERROR ALLOCATING CCEs\n", __func__, frameP, subframeP);
    }
  }

  stop_meas(&(eNB->eNB_scheduler));
  VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_ENB_DLSCH_ULSCH_SCHEDULER, VCD_FUNCTION_OUT);
}
