/*******************************************************************************
    OpenAirInterface 
    Copyright(c) 1999 - 2014 Eurecom

    OpenAirInterface is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.


    OpenAirInterface is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with OpenAirInterface.The full GNU General Public License is 
    included in this distribution in the file called "COPYING". If not, 
    see <http://www.gnu.org/licenses/>.

   Contact Information
   OpenAirInterface Admin: openair_admin@eurecom.fr
   OpenAirInterface Tech : openair_tech@eurecom.fr
   OpenAirInterface Dev  : openair4g-devel@eurecom.fr
  
   Address      : Eurecom, Compus SophiaTech 450, route des chappes, 06451 Biot, France.

 *******************************************************************************/

/*! \file sched_dlsch.c
* \brief DLSCH decoding thread (RTAI)
* \author R. Knopp, F. Kaltenberger
* \date 2011
* \version 0.1
* \company Eurecom
* \email: knopp@eurecom.fr,florian.kaltenberger@eurecom.fr
* \note
* \warning
*/
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>

#include "rt_wrapper.h"

#include <sys/mman.h>

#include "PHY/types.h"
#include "PHY/defs.h"
#include "PHY/extern.h"

#include "SCHED/defs.h"

#include "MAC_INTERFACE/extern.h"

#ifdef CBMIMO1
#include "ARCH/CBMIMO1/DEVICE_DRIVER/cbmimo1_device.h"
#include "ARCH/CBMIMO1/DEVICE_DRIVER/extern.h"
#include "ARCH/CBMIMO1/DEVICE_DRIVER/defs.h"
#endif // CBMIMO1

#include "UTIL/LOG/vcd_signal_dumper.h"

#define DEBUG_PHY

/// Mutex for instance count on dlsch scheduling 
pthread_mutex_t dlsch_mutex[8];
/// Condition variable for dlsch thread
pthread_cond_t dlsch_cond[8];

pthread_t dlsch_threads[8];
pthread_attr_t attr_dlsch_threads;
unsigned char dlsch_thread_indices[8];

// activity indicators for harq_pid's
int dlsch_instance_cnt[8];
// process ids for cpu
int dlsch_cpuid[8];
// subframe number for each harq_pid (needed to store ack in right place for UL)
int dlsch_subframe[8];

extern int oai_exit;

/*
extern int dlsch_errors;
extern int dlsch_received;
extern int dlsch_errors_last;
extern int dlsch_received_last;
extern int dlsch_fer;
extern int current_dlsch_cqi;
*/

/** DLSCH Decoding Thread */
static void * dlsch_thread(void *param) {

  //unsigned long cpuid;
  unsigned char dlsch_thread_index = *((unsigned char *)param);
  unsigned int ret;
  uint8_t harq_pid;

  RTIME time_in,time_out;
#ifdef RTAI
  RT_TASK *task;
  char task_name[8];
#endif

  int eNB_id = 0, UE_id = 0;
  PHY_VARS_UE *phy_vars_ue = PHY_vars_UE_g[UE_id];

  if ((dlsch_thread_index <0) || (dlsch_thread_index>7)) {
      LOG_E(PHY,"[SCHED][DLSCH] Illegal dlsch_thread_index %d (%p)!!!!\n",dlsch_thread_index,param);
    return 0;
  }

#ifdef RTAI
  sprintf(task_name,"DLSCH%d",dlsch_thread_index);
  task = rt_task_init_schmod(nam2num(task_name), 0, 0, 0, SCHED_FIFO, 0xF);

  if (task==NULL) {
      LOG_E(PHY,"[SCHED][DLSCH] Problem starting dlsch_thread_index %d (%s)!!!!\n",dlsch_thread_index,task_name);
      return 0;
  }
  else {
      LOG_I(PHY,"[SCHED][DLSCH] dlsch_thread for process %d started with id %p\n",
            dlsch_thread_index,
            task);
  }
#endif

  mlockall(MCL_CURRENT | MCL_FUTURE);

  //rt_set_runnable_on_cpuid(task,1);
  //cpuid = rtai_cpuid();

#ifdef HARD_RT
  rt_make_hard_real_time();
#endif

  //dlsch_cpuid[dlsch_thread_index] = cpuid;

  while (!oai_exit){

    vcd_signal_dumper_dump_function_by_name(VCD_SIGNAL_DUMPER_FUNCTIONS_DLSCH_THREAD0+dlsch_thread_index,0);
    if (pthread_mutex_lock(&dlsch_mutex[dlsch_thread_index]) != 0) {
        LOG_E(PHY,"[SCHED][DLSCH] error locking mutex.\n");
    }
    else {
        
        while (dlsch_instance_cnt[dlsch_thread_index] < 0) {
            pthread_cond_wait(&dlsch_cond[dlsch_thread_index],&dlsch_mutex[dlsch_thread_index]);
        }
        
        if (pthread_mutex_unlock(&dlsch_mutex[dlsch_thread_index]) != 0) {	
            LOG_E(PHY,"[SCHED][DLSCH] error unlocking mutex.\n");
        }
    }
    vcd_signal_dumper_dump_function_by_name(VCD_SIGNAL_DUMPER_FUNCTIONS_DLSCH_THREAD0+dlsch_thread_index,1);    
    if (oai_exit) break;
    
    LOG_I(PHY,"[SCHED][DLSCH] Frame %d: Calling dlsch_decoding with dlsch_thread_index = %d\n",phy_vars_ue->frame,dlsch_thread_index);

    time_in = rt_get_time_ns();
    
    if (phy_vars_ue->frame < phy_vars_ue->dlsch_errors[eNB_id]) {
        phy_vars_ue->dlsch_errors[eNB_id]=0;
        phy_vars_ue->dlsch_received[eNB_id] = 0;
    }

    harq_pid = dlsch_thread_index;

    if (phy_vars_ue->dlsch_ue[eNB_id][0]) {

      //      rt_printk("[SCHED][DLSCH] Frame %d, slot %d, start %llu, end %llu, proc time: %llu ns\n",phy_vars_ue->frame,last_slot,time0,time1,(time1-time0));
     
        dlsch_unscrambling(&phy_vars_ue->lte_frame_parms,
                           0,
                           phy_vars_ue->dlsch_ue[eNB_id][0],
                           phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->G,
                           phy_vars_ue->lte_ue_pdsch_vars[eNB_id]->llr[0],
                           0,
                           dlsch_subframe[dlsch_thread_index]<<1);

        LOG_I(PHY,"[UE %d] PDSCH Calling dlsch_decoding for subframe %d, harq_pid %d, G%d\n", phy_vars_ue->Mod_id,dlsch_subframe[dlsch_thread_index], harq_pid,phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->G);

	vcd_signal_dumper_dump_function_by_name(VCD_SIGNAL_DUMPER_FUNCTIONS_DLSCH_DECODING0+dlsch_thread_index,1);
        ret = dlsch_decoding(phy_vars_ue,
                             phy_vars_ue->lte_ue_pdsch_vars[eNB_id]->llr[0],
                             &phy_vars_ue->lte_frame_parms,
                             phy_vars_ue->dlsch_ue[eNB_id][0],
                             phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid],
                             dlsch_subframe[dlsch_thread_index],
                             harq_pid,
                             1, // is_crnti
			     0);  // llr8_flag
	vcd_signal_dumper_dump_function_by_name(VCD_SIGNAL_DUMPER_FUNCTIONS_DLSCH_DECODING0+dlsch_thread_index,0);

      	LOG_D(PHY,"[UE  %d][PDSCH %x/%d] Frame %d subframe %d: PDSCH/DLSCH decoding iter %d (mcs %d, rv %d, TBS %d)\n",
              phy_vars_ue->Mod_id,
              phy_vars_ue->dlsch_ue[eNB_id][0]->rnti,harq_pid,
              phy_vars_ue->frame,dlsch_subframe[dlsch_thread_index],ret,
              phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->mcs,
              phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->rvidx,
              phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->TBS); 
      

        if (ret == (1+MAX_TURBO_ITERATIONS)) {
            phy_vars_ue->dlsch_errors[eNB_id]++;
	
#ifdef DEBUG_PHY
	LOG_I(PHY,"[UE  %d][PDSCH %x/%d] Frame %d subframe %d DLSCH in error (rv %d,mcs %d)\n",
	      phy_vars_ue->Mod_id,phy_vars_ue->dlsch_ue[eNB_id][0]->rnti,
	      harq_pid,phy_vars_ue->frame,dlsch_subframe[dlsch_thread_index],
	      phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->rvidx,
	      phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->mcs);
#endif
      }
      else {
	LOG_I(PHY,"[UE  %d][PDSCH %x/%d] Frame %d subframe %d: Received DLSCH (rv %d,mcs %d)\n",
	      phy_vars_ue->Mod_id,phy_vars_ue->dlsch_ue[eNB_id][0]->rnti,
	      harq_pid,phy_vars_ue->frame,dlsch_subframe[dlsch_thread_index],
	      phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->rvidx,
	      phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->mcs);
	
#ifdef OPENAIR2
	mac_xface->ue_send_sdu(phy_vars_ue->Mod_id,
			       0, // CC_id
                           phy_vars_ue->frame,
                           phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->b,
                           phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->TBS>>3,
                           eNB_id);
#endif
	phy_vars_ue->total_TBS[eNB_id] =  phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->TBS + phy_vars_ue->total_TBS[eNB_id];
	phy_vars_ue->total_received_bits[eNB_id] = phy_vars_ue->total_received_bits[eNB_id] + phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->TBS;
      }
    }

    // this is done in main thread
    /*    
    if (phy_vars_ue->frame % 100 == 0) {
      if ((phy_vars_ue->dlsch_received[eNB_id] - phy_vars_ue->dlsch_received_last[eNB_id]) != 0) 
	phy_vars_ue->dlsch_fer[eNB_id] = (100*(phy_vars_ue->dlsch_errors[eNB_id] - phy_vars_ue->dlsch_errors_last[eNB_id]))/(phy_vars_ue->dlsch_received[eNB_id] - phy_vars_ue->dlsch_received_last[eNB_id]);
      phy_vars_ue->dlsch_errors_last[eNB_id] = phy_vars_ue->dlsch_errors[eNB_id];
      phy_vars_ue->dlsch_received_last[eNB_id] = phy_vars_ue->dlsch_received[eNB_id];
    }
    */

    time_out = rt_get_time_ns();

#ifdef DEBUG_PHY
    LOG_I(PHY,"[UE  %d][PDSCH %x/%d] Frame %d subframe %d: PDSCH/DLSCH decoding iter %d (mcs %d, rv %d, TBS %d)\n",
	  phy_vars_ue->Mod_id,
	  phy_vars_ue->dlsch_ue[eNB_id][0]->rnti,harq_pid,
	  phy_vars_ue->frame,dlsch_subframe[dlsch_thread_index],ret,
	  phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->mcs,
	  phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->rvidx,
	  phy_vars_ue->dlsch_ue[eNB_id][0]->harq_processes[harq_pid]->TBS);
    if (phy_vars_ue->frame%100==0) {
      LOG_D(PHY,"[UE  %d][PDSCH %x] Frame %d subframe %d dlsch_errors %d, dlsch_received %d, dlsch_fer %d, current_dlsch_cqi %d\n",
	    phy_vars_ue->Mod_id,phy_vars_ue->dlsch_ue[eNB_id][0]->rnti,
	    phy_vars_ue->frame,dlsch_subframe[dlsch_thread_index],
	    phy_vars_ue->dlsch_errors[eNB_id],
	    phy_vars_ue->dlsch_received[eNB_id],
	    phy_vars_ue->dlsch_fer[eNB_id],
	    phy_vars_ue->PHY_measurements.wideband_cqi_tot[eNB_id]);
    }
#endif 
    
    if (pthread_mutex_lock(&dlsch_mutex[dlsch_thread_index]) != 0) {
        msg("[openair][SCHED][DLSCH] error locking mutex.\n");
    }
    else {
        dlsch_instance_cnt[dlsch_thread_index]--;
        
        if (pthread_mutex_unlock(&dlsch_mutex[dlsch_thread_index]) != 0) {	
            msg("[openair][SCHED][DLSCH] error unlocking mutex.\n");
        }
    }

  }

#ifdef HARD_RT
  rt_make_soft_real_time();
#endif

  msg("[openair][SCHED][DLSCH] DLSCH thread %d exiting\n",dlsch_thread_index);  

  return 0;
}

int init_dlsch_threads(void) {
  
    int error_code;
    struct sched_param p;
    unsigned char dlsch_thread_index;
    
    pthread_attr_init (&attr_dlsch_threads);
    pthread_attr_setstacksize(&attr_dlsch_threads,OPENAIR_THREAD_STACK_SIZE);
    //attr_dlsch_threads.priority = 1;
    
    p.sched_priority = OPENAIR_THREAD_PRIORITY;
    pthread_attr_setschedparam  (&attr_dlsch_threads, &p);
#ifndef RTAI_ISNT_POSIX
    pthread_attr_setschedpolicy (&attr_dlsch_threads, SCHED_FIFO);
#endif
    
    for(dlsch_thread_index=0;dlsch_thread_index<8;dlsch_thread_index++) {

        pthread_mutex_init(&dlsch_mutex[dlsch_thread_index],NULL);
        pthread_cond_init(&dlsch_cond[dlsch_thread_index],NULL);

        dlsch_instance_cnt[dlsch_thread_index] = -1;
        dlsch_thread_indices[dlsch_thread_index] = dlsch_thread_index;
        rt_printk("[openair][SCHED][DLSCH][INIT] Allocating DLSCH thread for dlsch_thread_index %d (%p)\n",dlsch_thread_index,&dlsch_thread_indices[dlsch_thread_index]);
        error_code = pthread_create(&dlsch_threads[dlsch_thread_index],
                                    &attr_dlsch_threads,
                                    dlsch_thread,
                                    (void *)&dlsch_thread_indices[dlsch_thread_index]);
      
        if (error_code!= 0) {
            rt_printk("[openair][SCHED][DLSCH][INIT] Could not allocate dlsch_thread %d, error %d\n",dlsch_thread_index,error_code);
            return(error_code);
        }
        else {
            rt_printk("[openair][SCHED][DLSCH][INIT] Allocate dlsch_thread %d successful\n",dlsch_thread_index);
        }
    }   
    return(0);
}

void cleanup_dlsch_threads(void) {

  unsigned char dlsch_thread_index;

  for(dlsch_thread_index=0;dlsch_thread_index<8;dlsch_thread_index++) {
      //  pthread_exit(&dlsch_threads[dlsch_thread_index]);
      rt_printk("[openair][SCHED][DLSCH] Scheduling dlsch_thread %d to exit\n",dlsch_thread_index);
      
      dlsch_instance_cnt[dlsch_thread_index] = 0;
      if (pthread_cond_signal(&dlsch_cond[dlsch_thread_index]) != 0)
          rt_printk("[openair][SCHED][DLSCH] ERROR pthread_cond_signal\n");
      else
          rt_printk("[openair][SCHED][DLSCH] Signalled dlsch_thread %d to exit\n",dlsch_thread_index);
    
      rt_printk("[openair][SCHED][DLSCH] Exiting ...\n");
      pthread_cond_destroy(&dlsch_cond[dlsch_thread_index]);
      pthread_mutex_destroy(&dlsch_mutex[dlsch_thread_index]);
  }
}