/*
 * Copyright (c) 2013-2018 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:       Semaphore functions
 *
 * -----------------------------------------------------------------------------
 */

#include "rtx_lib.h"

//  OS Runtime Object Memory Usage
#if ((defined(OS_OBJ_MEM_USAGE) && (OS_OBJ_MEM_USAGE != 0)))
osRtxObjectMemUsage_t osRtxSemaphoreMemUsage
    __attribute__((section(".data.os.semaphore.obj"))) = {0U, 0U, 0U};
#endif

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

/// Decrement Semaphore tokens.
/// \param[in]  semaphore       semaphore object.
/// \return 1 - success, 0 - failure.
static uint32_t SemaphoreTokenDecrement(os_semaphore_t *semaphore) {
#if (EXCLUSIVE_ACCESS == 0)
  uint32_t primask = __get_PRIMASK();
#endif
  uint32_t ret;

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

  if (semaphore->tokens != 0U) {
    semaphore->tokens--;
    ret = 1U;
  } else {
    ret = 0U;
  }

  if (primask == 0U) {
    __enable_irq();
  }
#else
  if (atomic_dec16_nz(&semaphore->tokens) != 0U) {
    ret = 1U;
  } else {
    ret = 0U;
  }
#endif

  return ret;
}

/// Increment Semaphore tokens.
/// \param[in]  semaphore       semaphore object.
/// \return 1 - success, 0 - failure.
static uint32_t SemaphoreTokenIncrement(os_semaphore_t *semaphore) {
#if (EXCLUSIVE_ACCESS == 0)
  uint32_t primask = __get_PRIMASK();
#endif
  uint32_t ret;

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

  if (semaphore->tokens < semaphore->max_tokens) {
    semaphore->tokens++;
    ret = 1U;
  } else {
    ret = 0U;
  }

  if (primask == 0U) {
    __enable_irq();
  }
#else
  if (atomic_inc16_lt(&semaphore->tokens, semaphore->max_tokens) <
      semaphore->max_tokens) {
    ret = 1U;
  } else {
    ret = 0U;
  }
#endif

  return ret;
}

//  ==== Post ISR processing ====

/// Semaphore post ISR processing.
/// \param[in]  semaphore       semaphore object.
static void osRtxSemaphorePostProcess(os_semaphore_t *semaphore) {
  os_thread_t *thread;

  // Check if Thread is waiting for a token
  if (semaphore->thread_list != NULL) {
    // Try to acquire token
    if (SemaphoreTokenDecrement(semaphore) != 0U) {
      // Wakeup waiting Thread with highest Priority
      thread = osRtxThreadListGet(osRtxObject(semaphore));
      osRtxThreadWaitExit(thread, (uint32_t)osOK, FALSE);
      EvrRtxSemaphoreAcquired(semaphore, semaphore->tokens);
    }
  }
}

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

/// Create and Initialize a Semaphore object.
/// \note API identical to osSemaphoreNew
static osSemaphoreId_t svcRtxSemaphoreNew(uint32_t max_count,
                                          uint32_t initial_count,
                                          const osSemaphoreAttr_t *attr) {
  os_semaphore_t *semaphore;
  uint8_t flags;
  const char *name;

  // Check parameters
  if ((max_count == 0U) || (max_count > osRtxSemaphoreTokenLimit) ||
      (initial_count > max_count)) {
    EvrRtxSemaphoreError(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]
    semaphore = attr->cb_mem;
    if (semaphore != NULL) {
      // lint -e(923) -e(9078) "cast from pointer to unsigned int" [MISRA Note
      // 7]
      if ((((uint32_t)semaphore & 3U) != 0U) ||
          (attr->cb_size < sizeof(os_semaphore_t))) {
        EvrRtxSemaphoreError(NULL, osRtxErrorInvalidControlBlock);
        // lint -e{904} "Return statement before end of function" [MISRA Note 1]
        return NULL;
      }
    } else {
      if (attr->cb_size != 0U) {
        EvrRtxSemaphoreError(NULL, osRtxErrorInvalidControlBlock);
        // lint -e{904} "Return statement before end of function" [MISRA Note 1]
        return NULL;
      }
    }
  } else {
    name = NULL;
    semaphore = NULL;
  }

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

  if (semaphore != NULL) {
    // Initialize control block
    semaphore->id = osRtxIdSemaphore;
    semaphore->flags = flags;
    semaphore->name = name;
    semaphore->thread_list = NULL;
    semaphore->tokens = (uint16_t)initial_count;
    semaphore->max_tokens = (uint16_t)max_count;

    // Register post ISR processing function
    osRtxInfo.post_process.semaphore = osRtxSemaphorePostProcess;

    EvrRtxSemaphoreCreated(semaphore, semaphore->name);
  } else {
    EvrRtxSemaphoreError(NULL, (int32_t)osErrorNoMemory);
  }

  return semaphore;
}

/// Get name of a Semaphore object.
/// \note API identical to osSemaphoreGetName
static const char *svcRtxSemaphoreGetName(osSemaphoreId_t semaphore_id) {
  os_semaphore_t *semaphore = osRtxSemaphoreId(semaphore_id);

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

  EvrRtxSemaphoreGetName(semaphore, semaphore->name);

  return semaphore->name;
}

/// Acquire a Semaphore token or timeout if no tokens are available.
/// \note API identical to osSemaphoreAcquire
static osStatus_t svcRtxSemaphoreAcquire(osSemaphoreId_t semaphore_id,
                                         uint32_t timeout) {
  os_semaphore_t *semaphore = osRtxSemaphoreId(semaphore_id);
  osStatus_t status;

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

  // Try to acquire token
  if (SemaphoreTokenDecrement(semaphore) != 0U) {
    EvrRtxSemaphoreAcquired(semaphore, semaphore->tokens);
    status = osOK;
  } else {
    // No token available
    if (timeout != 0U) {
      EvrRtxSemaphoreAcquirePending(semaphore, timeout);
      // Suspend current Thread
      if (osRtxThreadWaitEnter(osRtxThreadWaitingSemaphore, timeout)) {
        osRtxThreadListPut(osRtxObject(semaphore), osRtxThreadGetRunning());
      } else {
        EvrRtxSemaphoreAcquireTimeout(semaphore);
      }
      status = osErrorTimeout;
    } else {
      EvrRtxSemaphoreNotAcquired(semaphore);
      status = osErrorResource;
    }
  }

  return status;
}

/// Release a Semaphore token that was acquired by osSemaphoreAcquire.
/// \note API identical to osSemaphoreRelease
static osStatus_t svcRtxSemaphoreRelease(osSemaphoreId_t semaphore_id) {
  os_semaphore_t *semaphore = osRtxSemaphoreId(semaphore_id);
  os_thread_t *thread;
  osStatus_t status;

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

  // Check if Thread is waiting for a token
  if (semaphore->thread_list != NULL) {
    EvrRtxSemaphoreReleased(semaphore, semaphore->tokens);
    // Wakeup waiting Thread with highest Priority
    thread = osRtxThreadListGet(osRtxObject(semaphore));
    osRtxThreadWaitExit(thread, (uint32_t)osOK, TRUE);
    EvrRtxSemaphoreAcquired(semaphore, semaphore->tokens);
    status = osOK;
  } else {
    // Try to release token
    if (SemaphoreTokenIncrement(semaphore) != 0U) {
      EvrRtxSemaphoreReleased(semaphore, semaphore->tokens);
      status = osOK;
    } else {
      EvrRtxSemaphoreError(semaphore, osRtxErrorSemaphoreCountLimit);
      status = osErrorResource;
    }
  }

  return status;
}

/// Get current Semaphore token count.
/// \note API identical to osSemaphoreGetCount
static uint32_t svcRtxSemaphoreGetCount(osSemaphoreId_t semaphore_id) {
  os_semaphore_t *semaphore = osRtxSemaphoreId(semaphore_id);

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

  EvrRtxSemaphoreGetCount(semaphore, semaphore->tokens);

  return semaphore->tokens;
}

/// Delete a Semaphore object.
/// \note API identical to osSemaphoreDelete
static osStatus_t svcRtxSemaphoreDelete(osSemaphoreId_t semaphore_id) {
  os_semaphore_t *semaphore = osRtxSemaphoreId(semaphore_id);
  os_thread_t *thread;

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

  // Unblock waiting threads
  if (semaphore->thread_list != NULL) {
    do {
      thread = osRtxThreadListGet(osRtxObject(semaphore));
      osRtxThreadWaitExit(thread, (uint32_t)osErrorResource, FALSE);
    } while (semaphore->thread_list != NULL);
    osRtxThreadDispatch(NULL);
  }

  // Mark object as invalid
  semaphore->id = osRtxIdInvalid;

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

  EvrRtxSemaphoreDestroyed(semaphore);

  return osOK;
}

//  Service Calls definitions
// lint ++flb "Library Begin" [MISRA Note 11]
SVC0_3(SemaphoreNew, osSemaphoreId_t, uint32_t, uint32_t,
       const osSemaphoreAttr_t *)
SVC0_1(SemaphoreGetName, const char *, osSemaphoreId_t)
SVC0_2(SemaphoreAcquire, osStatus_t, osSemaphoreId_t, uint32_t)
SVC0_1(SemaphoreRelease, osStatus_t, osSemaphoreId_t)
SVC0_1(SemaphoreGetCount, uint32_t, osSemaphoreId_t)
SVC0_1(SemaphoreDelete, osStatus_t, osSemaphoreId_t)
// lint --flb "Library End"

//  ==== ISR Calls ====

/// Acquire a Semaphore token or timeout if no tokens are available.
/// \note API identical to osSemaphoreAcquire
__STATIC_INLINE
osStatus_t isrRtxSemaphoreAcquire(osSemaphoreId_t semaphore_id,
                                  uint32_t timeout) {
  os_semaphore_t *semaphore = osRtxSemaphoreId(semaphore_id);
  osStatus_t status;

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

  // Try to acquire token
  if (SemaphoreTokenDecrement(semaphore) != 0U) {
    EvrRtxSemaphoreAcquired(semaphore, semaphore->tokens);
    status = osOK;
  } else {
    // No token available
    EvrRtxSemaphoreNotAcquired(semaphore);
    status = osErrorResource;
  }

  return status;
}

/// Release a Semaphore token that was acquired by osSemaphoreAcquire.
/// \note API identical to osSemaphoreRelease
__STATIC_INLINE
osStatus_t isrRtxSemaphoreRelease(osSemaphoreId_t semaphore_id) {
  os_semaphore_t *semaphore = osRtxSemaphoreId(semaphore_id);
  osStatus_t status;

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

  // Try to release token
  if (SemaphoreTokenIncrement(semaphore) != 0U) {
    // Register post ISR processing
    osRtxPostProcess(osRtxObject(semaphore));
    EvrRtxSemaphoreReleased(semaphore, semaphore->tokens);
    status = osOK;
  } else {
    EvrRtxSemaphoreError(semaphore, osRtxErrorSemaphoreCountLimit);
    status = osErrorResource;
  }

  return status;
}

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

/// Create and Initialize a Semaphore object.
osSemaphoreId_t osSemaphoreNew(uint32_t max_count, uint32_t initial_count,
                               const osSemaphoreAttr_t *attr) {
  osSemaphoreId_t semaphore_id;

  EvrRtxSemaphoreNew(max_count, initial_count, attr);
  if (IsIrqMode() || IsIrqMasked()) {
    EvrRtxSemaphoreError(NULL, (int32_t)osErrorISR);
    semaphore_id = NULL;
  } else {
    semaphore_id = __svcSemaphoreNew(max_count, initial_count, attr);
  }
  return semaphore_id;
}

/// Get name of a Semaphore object.
const char *osSemaphoreGetName(osSemaphoreId_t semaphore_id) {
  const char *name;

  if (IsIrqMode() || IsIrqMasked()) {
    EvrRtxSemaphoreGetName(semaphore_id, NULL);
    name = NULL;
  } else {
    name = __svcSemaphoreGetName(semaphore_id);
  }
  return name;
}

/// Acquire a Semaphore token or timeout if no tokens are available.
osStatus_t osSemaphoreAcquire(osSemaphoreId_t semaphore_id, uint32_t timeout) {
  osStatus_t status;

  EvrRtxSemaphoreAcquire(semaphore_id, timeout);
  if (IsIrqMode() || IsIrqMasked()) {
    status = isrRtxSemaphoreAcquire(semaphore_id, timeout);
  } else {
    status = __svcSemaphoreAcquire(semaphore_id, timeout);
  }
  return status;
}

/// Release a Semaphore token that was acquired by osSemaphoreAcquire.
osStatus_t osSemaphoreRelease(osSemaphoreId_t semaphore_id) {
  osStatus_t status;

  EvrRtxSemaphoreRelease(semaphore_id);
  if (IsIrqMode() || IsIrqMasked()) {
    status = isrRtxSemaphoreRelease(semaphore_id);
  } else {
    status = __svcSemaphoreRelease(semaphore_id);
  }
  return status;
}

/// Get current Semaphore token count.
uint32_t osSemaphoreGetCount(osSemaphoreId_t semaphore_id) {
  uint32_t count;

  if (IsIrqMode() || IsIrqMasked()) {
    count = svcRtxSemaphoreGetCount(semaphore_id);
  } else {
    count = __svcSemaphoreGetCount(semaphore_id);
  }
  return count;
}

/// Delete a Semaphore object.
osStatus_t osSemaphoreDelete(osSemaphoreId_t semaphore_id) {
  osStatus_t status;

  EvrRtxSemaphoreDelete(semaphore_id);
  if (IsIrqMode() || IsIrqMasked()) {
    EvrRtxSemaphoreError(semaphore_id, (int32_t)osErrorISR);
    status = osErrorISR;
  } else {
    status = __svcSemaphoreDelete(semaphore_id);
  }
  return status;
}
