/*
 * Copyright (c) 2013-2021 Arm Limited. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the License); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 *
 * -----------------------------------------------------------------------------
 *
 * Project:     CMSIS-RTOS RTX
 * Title:       Timer functions
 *
 * -----------------------------------------------------------------------------
 */

#include "rtx_lib.h"


//  OS Runtime Object Memory Usage
#ifdef RTX_OBJ_MEM_USAGE
osRtxObjectMemUsage_t osRtxTimerMemUsage \
__attribute__((section(".data.os.timer.obj"))) =
{ 0U, 0U, 0U };
#endif


//  ==== Helper functions ====

/// Insert Timer into the Timer List sorted by Time.
/// \param[in]  timer           timer object.
/// \param[in]  tick            timer tick.
static void TimerInsert (os_timer_t *timer, uint32_t tick) {
  os_timer_t *prev, *next;

  prev = NULL;
  next = osRtxInfo.timer.list;
  while ((next != NULL) && (next->tick <= tick)) {
    tick -= next->tick;
    prev  = next;
    next  = next->next;
  }
  timer->tick = tick;
  timer->prev = prev;
  timer->next = next;
  if (next != NULL) {
    next->tick -= timer->tick;
    next->prev  = timer;
  }
  if (prev != NULL) {
    prev->next = timer;
  } else {
    osRtxInfo.timer.list = timer;
  }
}

/// Remove Timer from the Timer List.
/// \param[in]  timer           timer object.
static void TimerRemove (const os_timer_t *timer) {

  if (timer->next != NULL) {
    timer->next->tick += timer->tick;
    timer->next->prev  = timer->prev;
  }
  if (timer->prev != NULL) {
    timer->prev->next  = timer->next;
  } else {
    osRtxInfo.timer.list = timer->next;
  }
}

/// Unlink Timer from the Timer List Head.
/// \param[in]  timer           timer object.
static void TimerUnlink (const os_timer_t *timer) {

  if (timer->next != NULL) {
    timer->next->prev = timer->prev;
  }
  osRtxInfo.timer.list = timer->next;
}


//  ==== Library functions ====

/// Timer Tick (called each SysTick).
static void osRtxTimerTick (void) {
  os_thread_t *thread_running;
  os_timer_t  *timer;
  osStatus_t   status;

  timer = osRtxInfo.timer.list;
  if (timer == NULL) {
    //lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return;
  }

  thread_running = osRtxThreadGetRunning();

  timer->tick--;
  while ((timer != NULL) && (timer->tick == 0U)) {
    TimerUnlink(timer);
    status = osMessageQueuePut(osRtxInfo.timer.mq, &timer->finfo, 0U, 0U);
    if (status != osOK) {
      const os_thread_t *thread = osRtxThreadGetRunning();
      osRtxThreadSetRunning(osRtxInfo.thread.run.next);
      (void)osRtxKernelErrorNotify(osRtxErrorTimerQueueOverflow, timer);
      if (osRtxThreadGetRunning() == NULL) {
        if (thread_running == thread) {
          thread_running = NULL;
        }
      }
    }
    if (timer->type == osRtxTimerPeriodic) {
      TimerInsert(timer, timer->load);
    } else {
      timer->state = osRtxTimerStopped;
    }
    timer = osRtxInfo.timer.list;
  }

  osRtxThreadSetRunning(thread_running);
}

/// Setup Timer Thread objects.
//lint -esym(714,osRtxTimerSetup) "Referenced from library configuration"
//lint -esym(759,osRtxTimerSetup) "Prototype in header"
//lint -esym(765,osRtxTimerSetup) "Global scope"
int32_t osRtxTimerSetup (void) {
  int32_t ret = -1;

  if (osRtxMessageQueueTimerSetup() == 0) {
    osRtxInfo.timer.tick = osRtxTimerTick;
    ret = 0;
  }

  return ret;
}

/// Timer Thread
//lint -esym(714,osRtxTimerThread) "Referenced from library configuration"
//lint -esym(759,osRtxTimerThread) "Prototype in header"
//lint -esym(765,osRtxTimerThread) "Global scope"
__NO_RETURN void osRtxTimerThread (void *argument) {
  os_timer_finfo_t   finfo;
  osStatus_t         status;
  osMessageQueueId_t mq = (osMessageQueueId_t)argument;

  for (;;) {
    //lint -e{934} "Taking address of near auto variable"
    status = osMessageQueueGet(mq, &finfo, NULL, osWaitForever);
    if (status == osOK) {
      EvrRtxTimerCallback(finfo.func, finfo.arg);
      (finfo.func)(finfo.arg);
    }
  }
}

//  ==== Service Calls ====

/// Create and Initialize a timer.
/// \note API identical to osTimerNew
static osTimerId_t svcRtxTimerNew (osTimerFunc_t func, osTimerType_t type, void *argument, const osTimerAttr_t *attr) {
  os_timer_t *timer;
  uint8_t     flags;
  const char *name;

  // Check parameters
  if ((func == NULL) || ((type != osTimerOnce) && (type != osTimerPeriodic))) {
    EvrRtxTimerError(NULL, (int32_t)osErrorParameter);
    //lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return NULL;
  }

  // Process attributes
  if (attr != NULL) {
    name  = attr->name;
    //lint -e{9079} "conversion from pointer to void to pointer to other type" [MISRA Note 6]
    timer = attr->cb_mem;
    if (timer != NULL) {
      //lint -e(923) -e(9078) "cast from pointer to unsigned int" [MISRA Note 7]
      if ((((uint32_t)timer & 3U) != 0U) || (attr->cb_size < sizeof(os_timer_t))) {
        EvrRtxTimerError(NULL, osRtxErrorInvalidControlBlock);
        //lint -e{904} "Return statement before end of function" [MISRA Note 1]
        return NULL;
      }
    } else {
      if (attr->cb_size != 0U) {
        EvrRtxTimerError(NULL, osRtxErrorInvalidControlBlock);
        //lint -e{904} "Return statement before end of function" [MISRA Note 1]
        return NULL;
      }
    }
  } else {
    name  = NULL;
    timer = NULL;
  }

  // Allocate object memory if not provided
  if (timer == NULL) {
    if (osRtxInfo.mpi.timer != NULL) {
      //lint -e{9079} "conversion from pointer to void to pointer to other type" [MISRA Note 5]
      timer = osRtxMemoryPoolAlloc(osRtxInfo.mpi.timer);
    } else {
      //lint -e{9079} "conversion from pointer to void to pointer to other type" [MISRA Note 5]
      timer = osRtxMemoryAlloc(osRtxInfo.mem.common, sizeof(os_timer_t), 1U);
    }
#ifdef RTX_OBJ_MEM_USAGE
    if (timer != NULL) {
      uint32_t used;
      osRtxTimerMemUsage.cnt_alloc++;
      used = osRtxTimerMemUsage.cnt_alloc - osRtxTimerMemUsage.cnt_free;
      if (osRtxTimerMemUsage.max_used < used) {
        osRtxTimerMemUsage.max_used = used;
      }
    }
#endif
    flags = osRtxFlagSystemObject;
  } else {
    flags = 0U;
  }

  if (timer != NULL) {
    // Initialize control block
    timer->id         = osRtxIdTimer;
    timer->state      = osRtxTimerStopped;
    timer->flags      = flags;
    timer->type       = (uint8_t)type;
    timer->name       = name;
    timer->prev       = NULL;
    timer->next       = NULL;
    timer->tick       = 0U;
    timer->load       = 0U;
    timer->finfo.func = func;
    timer->finfo.arg  = argument;

    EvrRtxTimerCreated(timer, timer->name);
  } else {
    EvrRtxTimerError(NULL, (int32_t)osErrorNoMemory);
  }

  return timer;
}

/// Get name of a timer.
/// \note API identical to osTimerGetName
static const char *svcRtxTimerGetName (osTimerId_t timer_id) {
  os_timer_t *timer = osRtxTimerId(timer_id);

  // Check parameters
  if ((timer == NULL) || (timer->id != osRtxIdTimer)) {
    EvrRtxTimerGetName(timer, NULL);
    //lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return NULL;
  }

  EvrRtxTimerGetName(timer, timer->name);

  return timer->name;
}

/// Start or restart a timer.
/// \note API identical to osTimerStart
static osStatus_t svcRtxTimerStart (osTimerId_t timer_id, uint32_t ticks) {
  os_timer_t *timer = osRtxTimerId(timer_id);

  // Check parameters
  if ((timer == NULL) || (timer->id != osRtxIdTimer) || (ticks == 0U)) {
    EvrRtxTimerError(timer, (int32_t)osErrorParameter);
    //lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return osErrorParameter;
  }

  if (timer->state == osRtxTimerRunning) {
    TimerRemove(timer);
  } else {
    if (osRtxInfo.timer.tick == NULL) {
      EvrRtxTimerError(timer, (int32_t)osErrorResource);
      //lint -e{904} "Return statement before end of function" [MISRA Note 1]
      return osErrorResource;
    } else {
      timer->state = osRtxTimerRunning;
      timer->load  = ticks;
    }
  }

  TimerInsert(timer, ticks);

  EvrRtxTimerStarted(timer);

  return osOK;
}

/// Stop a timer.
/// \note API identical to osTimerStop
static osStatus_t svcRtxTimerStop (osTimerId_t timer_id) {
  os_timer_t *timer = osRtxTimerId(timer_id);

  // Check parameters
  if ((timer == NULL) || (timer->id != osRtxIdTimer)) {
    EvrRtxTimerError(timer, (int32_t)osErrorParameter);
    //lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return osErrorParameter;
  }

  // Check object state
  if (timer->state != osRtxTimerRunning) {
    EvrRtxTimerError(timer, (int32_t)osErrorResource);
    //lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return osErrorResource;
  }

  timer->state = osRtxTimerStopped;

  TimerRemove(timer);

  EvrRtxTimerStopped(timer);

  return osOK;
}

/// Check if a timer is running.
/// \note API identical to osTimerIsRunning
static uint32_t svcRtxTimerIsRunning (osTimerId_t timer_id) {
  os_timer_t *timer = osRtxTimerId(timer_id);
  uint32_t    is_running;

  // Check parameters
  if ((timer == NULL) || (timer->id != osRtxIdTimer)) {
    EvrRtxTimerIsRunning(timer, 0U);
    //lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return 0U;
  }

  if (timer->state == osRtxTimerRunning) {
    EvrRtxTimerIsRunning(timer, 1U);
    is_running = 1U;
  } else {
    EvrRtxTimerIsRunning(timer, 0U);
    is_running = 0;
  }

  return is_running;
}

/// Delete a timer.
/// \note API identical to osTimerDelete
static osStatus_t svcRtxTimerDelete (osTimerId_t timer_id) {
  os_timer_t *timer = osRtxTimerId(timer_id);

  // Check parameters
  if ((timer == NULL) || (timer->id != osRtxIdTimer)) {
    EvrRtxTimerError(timer, (int32_t)osErrorParameter);
    //lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return osErrorParameter;
  }

  if (timer->state == osRtxTimerRunning) {
    TimerRemove(timer);
  }

  // Mark object as inactive and invalid
  timer->state = osRtxTimerInactive;
  timer->id    = osRtxIdInvalid;

  // Free object memory
  if ((timer->flags & osRtxFlagSystemObject) != 0U) {
    if (osRtxInfo.mpi.timer != NULL) {
      (void)osRtxMemoryPoolFree(osRtxInfo.mpi.timer, timer);
    } else {
      (void)osRtxMemoryFree(osRtxInfo.mem.common, timer);
    }
#ifdef RTX_OBJ_MEM_USAGE
    osRtxTimerMemUsage.cnt_free++;
#endif
  }

  EvrRtxTimerDestroyed(timer);

  return osOK;
}

//  Service Calls definitions
//lint ++flb "Library Begin" [MISRA Note 11]
SVC0_4(TimerNew,       osTimerId_t,  osTimerFunc_t, osTimerType_t, void *, const osTimerAttr_t *)
SVC0_1(TimerGetName,   const char *, osTimerId_t)
SVC0_2(TimerStart,     osStatus_t,   osTimerId_t, uint32_t)
SVC0_1(TimerStop,      osStatus_t,   osTimerId_t)
SVC0_1(TimerIsRunning, uint32_t,     osTimerId_t)
SVC0_1(TimerDelete,    osStatus_t,   osTimerId_t)
//lint --flb "Library End"


//  ==== Public API ====

/// Create and Initialize a timer.
osTimerId_t osTimerNew (osTimerFunc_t func, osTimerType_t type, void *argument, const osTimerAttr_t *attr) {
  osTimerId_t timer_id;

  EvrRtxTimerNew(func, type, argument, attr);
  if (IsException() || IsIrqMasked()) {
    EvrRtxTimerError(NULL, (int32_t)osErrorISR);
    timer_id = NULL;
  } else {
    timer_id = __svcTimerNew(func, type, argument, attr);
  }
  return timer_id;
}

/// Get name of a timer.
const char *osTimerGetName (osTimerId_t timer_id) {
  const char *name;

  if (IsException() || IsIrqMasked()) {
    EvrRtxTimerGetName(timer_id, NULL);
    name = NULL;
  } else {
    name = __svcTimerGetName(timer_id);
  }
  return name;
}

/// Start or restart a timer.
osStatus_t osTimerStart (osTimerId_t timer_id, uint32_t ticks) {
  osStatus_t status;

  EvrRtxTimerStart(timer_id, ticks);
  if (IsException() || IsIrqMasked()) {
    EvrRtxTimerError(timer_id, (int32_t)osErrorISR);
    status = osErrorISR;
  } else {
    status = __svcTimerStart(timer_id, ticks);
  }
  return status;
}

/// Stop a timer.
osStatus_t osTimerStop (osTimerId_t timer_id) {
  osStatus_t status;

  EvrRtxTimerStop(timer_id);
  if (IsException() || IsIrqMasked()) {
    EvrRtxTimerError(timer_id, (int32_t)osErrorISR);
    status = osErrorISR;
  } else {
    status = __svcTimerStop(timer_id);
  }
  return status;
}

/// Check if a timer is running.
uint32_t osTimerIsRunning (osTimerId_t timer_id) {
  uint32_t is_running;

  if (IsException() || IsIrqMasked()) {
    EvrRtxTimerIsRunning(timer_id, 0U);
    is_running = 0U;
  } else {
    is_running = __svcTimerIsRunning(timer_id);
  }
  return is_running;
}

/// Delete a timer.
osStatus_t osTimerDelete (osTimerId_t timer_id) {
  osStatus_t status;

  EvrRtxTimerDelete(timer_id);
  if (IsException() || IsIrqMasked()) {
    EvrRtxTimerError(timer_id, (int32_t)osErrorISR);
    status = osErrorISR;
  } else {
    status = __svcTimerDelete(timer_id);
  }
  return status;
}
