/*
 * 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:       System functions
 *
 * -----------------------------------------------------------------------------
 */

#include "rtx_lib.h"


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

/// Put Object into ISR Queue.
/// \param[in]  object          object.
/// \return 1 - success, 0 - failure.
static uint32_t isr_queue_put (os_object_t *object) {
#if (EXCLUSIVE_ACCESS == 0)
  uint32_t primask = __get_PRIMASK();
#else
  uint32_t n;
#endif
  uint16_t max;
  uint32_t ret;

  max = osRtxInfo.isr_queue.max;

#if (EXCLUSIVE_ACCESS == 0)
  __disable_irq();

  if (osRtxInfo.isr_queue.cnt < max) {
    osRtxInfo.isr_queue.cnt++;
    osRtxInfo.isr_queue.data[osRtxInfo.isr_queue.in] = object;
    if (++osRtxInfo.isr_queue.in == max) {
      osRtxInfo.isr_queue.in = 0U;
    }
    ret = 1U;
  } else {
    ret = 0U;
  }
  
  if (primask == 0U) {
    __enable_irq();
  }
#else
  if (atomic_inc16_lt(&osRtxInfo.isr_queue.cnt, max) < max) {
    n = atomic_inc16_lim(&osRtxInfo.isr_queue.in, max);
    osRtxInfo.isr_queue.data[n] = object;
    ret = 1U;
  } else {
    ret = 0U;
  }
#endif

  return ret;
}

/// Get Object from ISR Queue.
/// \return object or NULL.
static os_object_t *isr_queue_get (void) {
#if (EXCLUSIVE_ACCESS != 0)
  uint32_t     n;
#endif
  uint16_t     max;
  os_object_t *ret;

  max = osRtxInfo.isr_queue.max;

#if (EXCLUSIVE_ACCESS == 0)
  __disable_irq();

  if (osRtxInfo.isr_queue.cnt != 0U) {
    osRtxInfo.isr_queue.cnt--;
    ret = osRtxObject(osRtxInfo.isr_queue.data[osRtxInfo.isr_queue.out]);
    if (++osRtxInfo.isr_queue.out == max) {
      osRtxInfo.isr_queue.out = 0U;
    }
  } else {
    ret = NULL;
  }

  __enable_irq();
#else
  if (atomic_dec16_nz(&osRtxInfo.isr_queue.cnt) != 0U) {
    n = atomic_inc16_lim(&osRtxInfo.isr_queue.out, max);
    ret = osRtxObject(osRtxInfo.isr_queue.data[n]);
  } else {
    ret = NULL;
  }
#endif

  return ret;
}


//  ==== Library Functions ====

/// Tick Handler.
//lint -esym(714,osRtxTick_Handler) "Referenced by Exception handlers"
//lint -esym(759,osRtxTick_Handler) "Prototype in header"
//lint -esym(765,osRtxTick_Handler) "Global scope"
void osRtxTick_Handler (void) {
  os_thread_t *thread;

  OS_Tick_AcknowledgeIRQ();
  osRtxInfo.kernel.tick++;

  // Process Thread Delays
  osRtxThreadDelayTick();

  osRtxThreadDispatch(NULL);

  // Process Timers
  if (osRtxInfo.timer.tick != NULL) {
    osRtxInfo.timer.tick();
  }

  // Check Round Robin timeout
  if (osRtxInfo.thread.robin.timeout != 0U) {
    thread = osRtxInfo.thread.run.next;
    if (thread != osRtxInfo.thread.robin.thread) {
      osRtxInfo.thread.robin.thread = thread;
      if (thread->delay == 0U) {
        // Reset Round Robin
        thread->delay = osRtxInfo.thread.robin.timeout;
      }
    }
    if (thread->delay != 0U) {
      thread->delay--;
    }
    if (thread->delay == 0U) {
      // Round Robin Timeout
      if (osRtxKernelGetState() == osRtxKernelRunning) {
        thread = osRtxInfo.thread.ready.thread_list;
        if ((thread != NULL) && (thread->priority == osRtxInfo.thread.robin.thread->priority)) {
          osRtxThreadListRemove(thread);
          osRtxThreadReadyPut(osRtxInfo.thread.robin.thread);
          EvrRtxThreadPreempted(osRtxInfo.thread.robin.thread);
          osRtxThreadSwitch(thread);
          osRtxInfo.thread.robin.thread = thread;
          thread->delay = osRtxInfo.thread.robin.timeout;
        }
      }
    }
  }
}

/// Pending Service Call Handler.
//lint -esym(714,osRtxPendSV_Handler) "Referenced by Exception handlers"
//lint -esym(759,osRtxPendSV_Handler) "Prototype in header"
//lint -esym(765,osRtxPendSV_Handler) "Global scope"
void osRtxPendSV_Handler (void) {
  os_object_t *object;

  for (;;) {
    object = isr_queue_get();
    if (object == NULL) {
      break;
    }
    switch (object->id) {
      case osRtxIdThread:
        osRtxInfo.post_process.thread(osRtxThreadObject(object));
        break;
      case osRtxIdEventFlags:
        osRtxInfo.post_process.event_flags(osRtxEventFlagsObject(object));
        break;
      case osRtxIdSemaphore:
        osRtxInfo.post_process.semaphore(osRtxSemaphoreObject(object));
        break;
      case osRtxIdMemoryPool:
        osRtxInfo.post_process.memory_pool(osRtxMemoryPoolObject(object));
        break;
      case osRtxIdMessage:
        osRtxInfo.post_process.message(osRtxMessageObject(object));
        break;
      default:
        // Should never come here
        break;
    }
  }

  osRtxThreadDispatch(NULL);
}

/// Register post ISR processing.
/// \param[in]  object          generic object.
void osRtxPostProcess (os_object_t *object) {

  if (isr_queue_put(object) != 0U) {
    if (osRtxInfo.kernel.blocked == 0U) {
      SetPendSV();
    } else {
      osRtxInfo.kernel.pendSV = 1U;
    }
  } else {
    (void)osRtxKernelErrorNotify(osRtxErrorISRQueueOverflow, object);
  }
}
