/**
 *  @file tpl_as_app_kernel.c
 *
 *  @section desc File description
 *
 *  Implementation of Autosar OS Application Kernel
 *
 *  @section copyright Copyright
 *
 *  Trampoline OS
 *
 *  Trampoline is copyright (c) IRCCyN 2005-2009
 *  Autosar extension is copyright (c) IRCCyN and ESEO 2007-2009
 *  Trampoline and its Autosar extension are protected by the
 *  French intellectual property law.
 *
 * This software is distributed under the Lesser GNU Public Licence
 *
 *  @section infos File informations
 *
 *  $Date$
 *  $Rev$
 *  $Author$
 *  $URL$
 */

#include "tpl_as_app_kernel.h"
#include "tpl_os_kernel.h"
#include "tpl_os_task_kernel.h"
#include "tpl_os_alarm_kernel.h"
#include "tpl_os_resource_kernel.h"
#include "tpl_os_definitions.h"
#include "tpl_machine_interface.h"
#include "tpl_as_counter_kernel.h"
#include "tpl_as_st_kernel.h"
#include "tpl_as_definitions.h"
#include "tpl_as_error.h"
#if SPINLOCK_COUNT > 0
#include "tpl_as_spinlock_kernel.h"
#endif

#include "tpl_os_task.h"
DeclareTask(INVALID_TASK);

#include "tpl_dow.h"


#define OS_START_SEC_CONST_UNSPECIFIED
#include "tpl_memmap.h"

static CONST(tpl_generic_id, AUTOMATIC) tpl_obj_count_table[6] = {
  TASK_COUNT+ISR_COUNT,
  ALARM_COUNT,
  RESOURCE_COUNT,
  COUNTER_COUNT,
  SCHEDTABLE_COUNT,
  IOC_COUNT
};

#define OS_STOP_SEC_CONST_UNSPECIFIED
#include "tpl_memmap.h"


#define OS_START_SEC_CODE
#include "tpl_memmap.h"
/**
 *  Get the application ID to which the current process belongs to
 *
 *  @retval   the application ID (OS261) or INVALID_OSAPPLICATION (OS262)
 */
FUNC(tpl_app_id, OS_CODE) tpl_get_application_id_service(void)
{
  GET_CURRENT_CORE_ID(core_id)
  
  VAR(StatusType, AUTOMATIC) result_status = E_OK;
  VAR(tpl_app_id, AUTOMATIC) result = INVALID_OSAPPLICATION_ID;

  /*  lock the kernel    */
  LOCK_KERNEL()

  /* check interrupts are not disabled by user    */
  CHECK_INTERRUPT_LOCK(result_status)

  /*  store information for error hook routine    */
  STORE_SERVICE(OSServiceId_GetApplicationID)

#if APP_COUNT > 0
  result =  TPL_KERN(core_id).s_running->app_id;
#endif

  PROCESS_ERROR(result_status)

  /*  unlock the kernel  */
  UNLOCK_KERNEL()

  return result;

}

/**
 *  Get the application ID to which the object belongs to
 *
 *  @param    obj_type  the type of object. @see #ObjectTypeType
 *  @param    obj_id    the id of the object. @see #tpl_generic_id
 *
 *  @retval   the application ID (OS273) or INVALID_OSAPPLICATION (OS274, OS319)
 */
FUNC(tpl_app_id, OS_CODE) tpl_check_object_ownership_service(
  ObjectTypeType  obj_type,
  tpl_generic_id  obj_id)
{
  VAR(tpl_app_id, AUTOMATIC) result = INVALID_OSAPPLICATION_ID;
  VAR(StatusType, AUTOMATIC) result_status = E_OK;
  GET_CURRENT_CORE_ID(core_id)

  /*  lock the kernel  */
  LOCK_KERNEL()

  /* check interrupts are not disabled by user    */
  CHECK_INTERRUPT_LOCK(result_status)

  /*  store information for error hook routine    */
  STORE_SERVICE(OSServiceId_CheckObjectOwnership)
  STORE_OBJECT_TYPE(obj_type)
  STORE_OBJECT_ID(obj_id)

#if APP_COUNT > 0
  switch (obj_type) {

    case OBJECT_TASK: /*  Same as OBJECT_ISR  */
#if (TASK_COUNT > 0) || (ISR_COUNT > 0)
      if (obj_id < (TASK_COUNT+ISR_COUNT))
      {
      result = tpl_stat_proc_table[obj_id]->app_id;
      }
#endif
      break;

    case OBJECT_ALARM:
#if ALARM_COUNT > 0
      if (obj_id < ALARM_COUNT)
      {
      P2CONST(tpl_time_obj_static, AUTOMATIC, OS_CONST) alr =
        tpl_alarm_table[obj_id]->stat_part;
      result = alr->app_id;
      }
#endif
      break;

    case OBJECT_RESOURCE:
#if RESOURCE_COUNT > 1
      if (obj_id < RESOURCE_COUNT)
      {
      result = TPL_RESOURCE_TABLE(core_id)[obj_id]->app_id;
      }
#endif
      break;

    case OBJECT_COUNTER:
#if COUNTER_COUNT > 0
      if (obj_id < COUNTER_COUNT)
      {
      result = tpl_counter_table[obj_id]->app_id;
      }
#endif
      break;

    case OBJECT_SCHEDULETABLE:
#if SCHEDTABLE_COUNT > 0
      if (obj_id < SCHEDTABLE_COUNT)
      {
      P2CONST(tpl_time_obj_static, AUTOMATIC, OS_CONST) st =
        tpl_schedtable_table[obj_id]->b_desc.stat_part;
      result = st->app_id;
      }
#endif
      break;

    default:
      result = INVALID_OSAPPLICATION_ID;
    }
#endif

  PROCESS_ERROR(result_status)

  /*  unlock the kernel  */
  UNLOCK_KERNEL()

  return result;
}

/**
 *  Check an Application may access an object
 *
 *  @param    app_id    the id of the application
 *  @param    obj_type  the type of object. @see #ObjectTypeType
 *  @param    obj_id    the id of the object. @see #tpl_generic_id
 *
 *  @retval   ACCESS (OS271)    if the application has access to the object
 *                              or if the object is Res_scheduler
 *  @retval   NO_ACCESS (OS272) if the application has no access to the object
 *                              or one of the parameters is invalid
 */
FUNC(uint8, OS_CODE) tpl_check_object_access_service(
  tpl_app_id      app_id,
  ObjectTypeType  obj_type, /*uint8 before*/
  tpl_generic_id  obj_id)
{
  VAR(uint8, AUTOMATIC) result = NO_ACCESS;
  VAR(StatusType, AUTOMATIC) result_status = E_OK;

  LOCK_KERNEL()

  /* check interrupts are not disabled by user    */
  CHECK_INTERRUPT_LOCK(result_status)

  STORE_SERVICE(OSServiceId_CheckObjectAccess)
  STORE_APPLICATION_ID(app_id)
  STORE_OBJECT_TYPE(obj_type)
  STORE_OBJECT_ID(obj_id)

#if APP_COUNT > 0
    if ((app_id < APP_COUNT) &&
      (obj_type < OBJECT_TYPE_COUNT) &&
      (obj_id < tpl_obj_count_table[obj_type]))
    {
    /*  Get the access vector of the corresponding application  */
      CONSTP2CONST(tpl_app_access, AUTOMATIC, OS_APPL_CONST) app_access =
        tpl_app_table[app_id];
      CONST(uint8, AUTOMATIC) bit_shift = (uint8)(obj_id & 0x7);
      CONST(uint8, AUTOMATIC) byte_idx = (uint8)(obj_id >> 3);
      result = (uint8)(((app_access->access_vec[obj_type][byte_idx]) >> bit_shift) & 0x1);
    }
#endif

  PROCESS_ERROR(result_status)

  UNLOCK_KERNEL()

  return result;
}

/**
 *  Terminate an OS Application. All running processes/alarms/schedule tables
 *  are killed
 *
 *  @param    app_id    the id of the application
 *  @param    restart_opt   indicates if the OS Application should be restarted.
 *                          @see #RestartType
 *
 *  @retval   E_OK          called from allowed context (OS287).
 *  @retval   E_OS_CALLEVEL wrong context (OS288)
 *  @retval   E_OS_VALUE    invalid restart_opt (OS459)
 */
FUNC(tpl_status, OS_CODE) tpl_terminate_application_service(
  tpl_app_id  app_id,
  uint8       restart_opt)
{
  GET_CURRENT_CORE_ID(core_id)
  GET_TPL_KERN_FOR_CORE_ID(core_id, a_tpl_kern)

  VAR(tpl_status, AUTOMATIC) result = E_OK;
#if APP_COUNT > 0
  VAR(tpl_proc_id, AUTOMATIC) restart_id;
#endif /*APP_COUNT*/

  LOCK_KERNEL()

  /* check interrupts are not disabled by user */
  CHECK_INTERRUPT_LOCK(result)

  /* store information for error hook routine */
  STORE_SERVICE(OSServiceId_TerminateApplication)
  STORE_TERMAPP_OPT(restart_opt)

#if APP_COUNT > 0
  IF_NO_EXTENDED_ERROR(result)
  {
    restart_id = tpl_app_table[app_id]->restart;

    DOW_DO(printf("CALLING TerminateApplication");)


    if ((restart_opt == RESTART) || (restart_opt == NO_RESTART))
    {
      if (app_id < APP_COUNT)
      {

        /*
         * upadte the state of the application
         */
        if( restart_opt == RESTART )
        {
          tpl_app_dyn_table[app_id].state = APPLICATION_RESTARTING;
        }
        else
        {
          tpl_app_dyn_table[app_id].state = APPLICATION_TERMINATED;
        }

        /*
         * First, remove all alarms belonging
         * to the OS application from the queue
         */
#if ALARM_COUNT > 0
        {
          P2CONST(tpl_alarm_id, AUTOMATIC, OS_APPL_CONST) alarms =
          tpl_app_table[app_id]->alarms;
          CONST(tpl_alarm_id, AUTOMATIC) alarm_count =
          tpl_app_table[app_id]->alarm_count;
          VAR(tpl_alarm_id, AUTOMATIC) i;
          for (i = 0; i < alarm_count; i++)
          {
            CONST(tpl_alarm_id, AUTOMATIC) alarm_id = alarms[i];
            P2VAR(tpl_time_obj, AUTOMATIC, OS_APPL_DATA) alarm =
              tpl_alarm_table[alarm_id];
            DOW_DO(printf("Removing alarm %d\n",(int)alarm_id);)
            if (alarm->state == ALARM_ACTIVE)
            {
              tpl_remove_time_obj(alarm);
              alarm->state = ALARM_SLEEP;
            }
          }
        }
#endif
        /*
         * Then remove all the schedule tables belonging
         * to the OS application from the queue
         */
#if SCHEDTABLE_COUNT > 0
        {
          P2CONST(tpl_schedtable_id, AUTOMATIC, OS_APPL_CONST) schedtables =
          tpl_app_table[app_id]->sts;
          CONST(tpl_schedtable_id, AUTOMATIC) schedtable_count =
          tpl_app_table[app_id]->st_count;
          VAR(tpl_schedtable_id, AUTOMATIC) i;
          for (i = 0; i < schedtable_count; i++)
          {
            CONST(tpl_schedtable_id, AUTOMATIC) schedtable_id = schedtables[i];
            P2VAR(tpl_schedule_table, AUTOMATIC, OS_APPL_DATA) schedtable =
              tpl_schedtable_table[schedtable_id];
            if (schedtable->b_desc.state != SCHEDULETABLE_STOPPED)
            {
              tpl_remove_time_obj(&(schedtable->b_desc));
              schedtable->b_desc.state = SCHEDULETABLE_STOPPED;
              schedtable->index = 0;
            }
          }
        }
#endif
#if (TASK_COUNT > 0) || (ISR_COUNT > 0)
        /*
         * Then remove all processes belonging to the OS
         * application in the ready list and in the waiting
         * state (the running process called this service)
         */
        {
          P2CONST(tpl_proc_id, AUTOMATIC, OS_APPL_CONST) procs =
          tpl_app_table[app_id]->procs;
          CONST(tpl_proc_id, AUTOMATIC) proc_count =
          tpl_app_table[app_id]->proc_count;
          VAR(tpl_proc_id, AUTOMATIC) i;
          for (i = 0; i < proc_count; i++)
          {
            CONST(tpl_proc_id, AUTOMATIC) proc_id = procs[i];
            /* remove the process from the ready queue */
            tpl_remove_proc(proc_id);
            /*
             * release the resources, both external
             * and internal, that could be held
             */
#if RESOURCE_COUNT > 0
            tpl_release_all_resources(proc_id);
#endif
            tpl_release_internal_resource(proc_id);
            /* reset the task descriptor */
            tpl_dyn_proc_table[proc_id]->state = SUSPENDED;
            tpl_dyn_proc_table[proc_id]->activate_count = 0;
            tpl_dyn_proc_table[proc_id]->priority =
            tpl_stat_proc_table[proc_id]->base_priority;
          }
        }
#endif
#if SPINLOCK_COUNT > 0
        /*
         * Then release all the spinlocks that has been taken by the core that
         * has its application terminated.
         */
        {
          RELEASE_ALL_SPINLOCKS(tpl_core_id_for_app[app_id]);
        }
#endif

        /* Restart the application and call the restart task if needed  */
        if ((RESTART == restart_opt) &&
            (restart_id != INVALID_TASK))
        {
          result = tpl_activate_task(restart_id);
        }

        if (TPL_KERN_REF(a_tpl_kern).s_running->app_id == app_id)
        {
          /*
           * if running task is part of terminating application,
           * it must be killed. In this case the restart task runs on the same
           * core so everything happens on the current core.
           */
          TPL_KERN_REF(a_tpl_kern).running->activate_count--;
          tpl_terminate();
          TPL_KERN_REF(a_tpl_kern).need_switch = NEED_SWITCH;
          tpl_start(CORE_ID_OR_NOTHING(core_id));
        }
        else if (restart_id != INVALID_TASK)
        {
          /*
           * The running task is not part of the terminating application.
           * So the restart task, if any, may have triggered a rescheduling
           * on the core it belongs to.
           */
          GET_PROC_CORE_ID(restart_id, restart_core_id)

          if (TPL_KERN(restart_core_id).need_schedule)
          {
            tpl_schedule_from_running(CORE_ID_OR_NOTHING(restart_core_id));
          }
        }

      LOCAL_SWITCH_CONTEXT_NOSAVE(core_id)

      }
      else
      {
        result = E_OS_CALLEVEL;
      }
    }
    else
    {
     result = E_OS_VALUE;
    }
  }
#else
  IF_NO_EXTENDED_ERROR(result)
  {
    /* return E_OS_CALLEVEL when no OS-Application ?*/
    result = E_OS_CALLEVEL;
  }
#endif

  PROCESS_ERROR(result)

  /*  unlock the kernel  */
  UNLOCK_KERNEL()

  return result;
}


/**
 *  Allow other OsApplication to access an OsApplication after restarting
 *
 *  @retval   E_OK
 *  @retval   E_OS_STATE called from an OsApplication which state is not APPLICATION_RESTARTING
 */
FUNC(tpl_status, OS_CODE) tpl_allow_access_service(void)
{
  GET_CURRENT_CORE_ID(core_id)
    
  VAR(tpl_status, AUTOMATIC) result = E_OK;
  VAR(tpl_app_id, AUTOMATIC) app_id;
  VAR(tpl_app_state, AUTOMATIC) app_state;

  LOCK_KERNEL()

  /* check interrupts are not disabled by user */
  CHECK_INTERRUPT_LOCK(result)

  /* store information for error hook routine */
  STORE_SERVICE(OSServiceId_AllowAccess)

  IF_NO_EXTENDED_ERROR(result)
  {
    app_id = TPL_KERN(core_id).s_running->app_id;

    if (app_id < APP_COUNT)
    {
      app_state = tpl_app_dyn_table[app_id].state;
      if(app_state == APPLICATION_RESTARTING)
      {
        tpl_app_dyn_table[app_id].state = APPLICATION_ACCESSIBLE;
      }
      else
      {
        result = E_OS_STATE;
      }
    }
    else
    {
      result = E_OS_VALUE;
    }
  }

  PROCESS_ERROR(result)

  /*  unlock the kernel  */
  UNLOCK_KERNEL()

  return result;
}


/**
 *  Allow other OsApplication to access an OsApplication after restarting
 *
 *  @retval   E_OK
 *  @retval   E_OS_STATE called from an OsApplication which state is not APPLICATION_RESTARTING
 */
FUNC(tpl_status, OS_CODE) tpl_get_application_state_service(
  VAR(tpl_app_id, AUTOMATIC) app_id,
  P2VAR(tpl_app_state, AUTOMATIC, OS_VAR) app_state)
{
  VAR(tpl_status, AUTOMATIC) result = E_OK;

  LOCK_KERNEL()

  /* check interrupts are not disabled by user */
  CHECK_INTERRUPT_LOCK(result)

  /* store information for error hook routine */
  STORE_SERVICE(OSServiceId_GetApplicationState)

  STORE_APPLICATION_ID(app_id)

  IF_NO_EXTENDED_ERROR(result)
  {
    if (app_id < APP_COUNT)
    {
      *app_state = tpl_app_dyn_table[app_id].state;
    }
    else
    {
      result = E_OS_ID;
    }
  }

  PROCESS_ERROR(result)

  /*  unlock the kernel  */
  UNLOCK_KERNEL()

  return result;
}


/**
 * Starts all OsApplications
 */
FUNC(void, OS_CODE) tpl_start_apps(void)
{
  VAR(tpl_app_id, AUTOMATIC) i;

  for(i=0; i<APP_COUNT; i++)
  {
    tpl_app_dyn_table[i].state = APPLICATION_ACCESSIBLE;
  }
}


#if 1 == 0
#if APP_COUNT > 0
#  if WITH_MEMORY_PROTECTION == NO
/**
 *  Calls the StartupHook of all OS Applications
 *
 */
FUNC(void, OS_CODE) tpl_osapp_startup_hooks(void)
{
  /*
   * Without memory protection, it is simply function calls
   */
  VAR(int, AUTOMATIC) i;

  for (i=0; i<APP_COUNT; i++)
  {
    tpl_application_hook hook = tpl_app_table[i]->startup_hook;
    if (hook != NULL)
    {
      hook();
    }
  }
}

/**
 *  Calls the ShutdownHook of all OS Applications
 *
 */
FUNC(void, OS_CODE) tpl_osapp_shutdown_hooks(void)
{
  /*
   * Without memory protection, it is simply function calls
   */
  VAR(int, AUTOMATIC) i;

  for (i=0; i<APP_COUNT; i++)
  {
    tpl_application_hook hook = tpl_app_table[i]->shutdown_hook;
    if (hook != NULL)
    {
      hook();
    }
  }
}
#  endif /* WITH_MEMORY_PROTECTION == NO */
#endif /* APP_COUNT > 0 */
#endif

#define OS_STOP_SEC_CODE
#include "tpl_memmap.h"

/*  End of file tpl_as_app_kernel.c  */