/*
 * Copyright (c) 2013-2019 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:       Event Flags functions
 *
 * -----------------------------------------------------------------------------
 */

#include "rtx_lib.h"

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

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

/// Set Event Flags.
/// \param[in]  ef              event flags object.
/// \param[in]  flags           specifies the flags to set.
/// \return event flags after setting.
static uint32_t EventFlagsSet(os_event_flags_t *ef, uint32_t flags) {
#if (EXCLUSIVE_ACCESS == 0)
  uint32_t primask = __get_PRIMASK();
#endif
  uint32_t event_flags;

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

  ef->event_flags |= flags;
  event_flags = ef->event_flags;

  if (primask == 0U) {
    __enable_irq();
  }
#else
  event_flags = atomic_set32(&ef->event_flags, flags);
#endif

  return event_flags;
}

/// Clear Event Flags.
/// \param[in]  ef              event flags object.
/// \param[in]  flags           specifies the flags to clear.
/// \return event flags before clearing.
static uint32_t EventFlagsClear(os_event_flags_t *ef, uint32_t flags) {
#if (EXCLUSIVE_ACCESS == 0)
  uint32_t primask = __get_PRIMASK();
#endif
  uint32_t event_flags;

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

  event_flags = ef->event_flags;
  ef->event_flags &= ~flags;

  if (primask == 0U) {
    __enable_irq();
  }
#else
  event_flags = atomic_clr32(&ef->event_flags, flags);
#endif

  return event_flags;
}

/// Check Event Flags.
/// \param[in]  ef              event flags object.
/// \param[in]  flags           specifies the flags to check.
/// \param[in]  options         specifies flags options (osFlagsXxxx).
/// \return event flags before clearing or 0 if specified flags have not been
/// set.
static uint32_t EventFlagsCheck(os_event_flags_t *ef, uint32_t flags,
                                uint32_t options) {
#if (EXCLUSIVE_ACCESS == 0)
  uint32_t primask;
#endif
  uint32_t event_flags;

  if ((options & osFlagsNoClear) == 0U) {
#if (EXCLUSIVE_ACCESS == 0)
    primask = __get_PRIMASK();
    __disable_irq();

    event_flags = ef->event_flags;
    if ((((options & osFlagsWaitAll) != 0U) &&
         ((event_flags & flags) != flags)) ||
        (((options & osFlagsWaitAll) == 0U) && ((event_flags & flags) == 0U))) {
      event_flags = 0U;
    } else {
      ef->event_flags &= ~flags;
    }

    if (primask == 0U) {
      __enable_irq();
    }
#else
    if ((options & osFlagsWaitAll) != 0U) {
      event_flags = atomic_chk32_all(&ef->event_flags, flags);
    } else {
      event_flags = atomic_chk32_any(&ef->event_flags, flags);
    }
#endif
  } else {
    event_flags = ef->event_flags;
    if ((((options & osFlagsWaitAll) != 0U) &&
         ((event_flags & flags) != flags)) ||
        (((options & osFlagsWaitAll) == 0U) && ((event_flags & flags) == 0U))) {
      event_flags = 0U;
    }
  }

  return event_flags;
}

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

/// Event Flags post ISR processing.
/// \param[in]  ef              event flags object.
static void osRtxEventFlagsPostProcess(os_event_flags_t *ef) {
  os_thread_t *thread;
  os_thread_t *thread_next;
  uint32_t event_flags;

  // Check if Threads are waiting for Event Flags
  thread = ef->thread_list;
  while (thread != NULL) {
    thread_next = thread->thread_next;
    event_flags =
        EventFlagsCheck(ef, thread->wait_flags, thread->flags_options);
    if (event_flags != 0U) {
      osRtxThreadListRemove(thread);
      osRtxThreadWaitExit(thread, event_flags, FALSE);
      EvrRtxEventFlagsWaitCompleted(ef, thread->wait_flags,
                                    thread->flags_options, event_flags);
    }
    thread = thread_next;
  }
}

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

/// Create and Initialize an Event Flags object.
/// \note API identical to osEventFlagsNew
static osEventFlagsId_t svcRtxEventFlagsNew(const osEventFlagsAttr_t *attr) {
  os_event_flags_t *ef;
  uint8_t flags;
  const char *name;

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

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

  if (ef != NULL) {
    // Initialize control block
    ef->id = osRtxIdEventFlags;
    ef->flags = flags;
    ef->name = name;
    ef->thread_list = NULL;
    ef->event_flags = 0U;

    // Register post ISR processing function
    osRtxInfo.post_process.event_flags = osRtxEventFlagsPostProcess;

    EvrRtxEventFlagsCreated(ef, ef->name);
  } else {
    EvrRtxEventFlagsError(NULL, (int32_t)osErrorNoMemory);
  }

  return ef;
}

/// Get name of an Event Flags object.
/// \note API identical to osEventFlagsGetName
static const char *svcRtxEventFlagsGetName(osEventFlagsId_t ef_id) {
  os_event_flags_t *ef = osRtxEventFlagsId(ef_id);

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

  EvrRtxEventFlagsGetName(ef, ef->name);

  return ef->name;
}

/// Set the specified Event Flags.
/// \note API identical to osEventFlagsSet
static uint32_t svcRtxEventFlagsSet(osEventFlagsId_t ef_id, uint32_t flags) {
  os_event_flags_t *ef = osRtxEventFlagsId(ef_id);
  os_thread_t *thread;
  os_thread_t *thread_next;
  uint32_t event_flags;
  uint32_t event_flags0;

  // Check parameters
  if ((ef == NULL) || (ef->id != osRtxIdEventFlags) ||
      ((flags & ~(((uint32_t)1U << osRtxEventFlagsLimit) - 1U)) != 0U)) {
    EvrRtxEventFlagsError(ef, (int32_t)osErrorParameter);
    // lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return ((uint32_t)osErrorParameter);
  }

  // Set Event Flags
  event_flags = EventFlagsSet(ef, flags);

  // Check if Threads are waiting for Event Flags
  thread = ef->thread_list;
  while (thread != NULL) {
    thread_next = thread->thread_next;
    event_flags0 =
        EventFlagsCheck(ef, thread->wait_flags, thread->flags_options);
    if (event_flags0 != 0U) {
      if ((thread->flags_options & osFlagsNoClear) == 0U) {
        event_flags = event_flags0 & ~thread->wait_flags;
      } else {
        event_flags = event_flags0;
      }
      osRtxThreadListRemove(thread);
      osRtxThreadWaitExit(thread, event_flags0, FALSE);
      EvrRtxEventFlagsWaitCompleted(ef, thread->wait_flags,
                                    thread->flags_options, event_flags0);
    }
    thread = thread_next;
  }
  osRtxThreadDispatch(NULL);

  EvrRtxEventFlagsSetDone(ef, event_flags);

  return event_flags;
}

/// Clear the specified Event Flags.
/// \note API identical to osEventFlagsClear
static uint32_t svcRtxEventFlagsClear(osEventFlagsId_t ef_id, uint32_t flags) {
  os_event_flags_t *ef = osRtxEventFlagsId(ef_id);
  uint32_t event_flags;

  // Check parameters
  if ((ef == NULL) || (ef->id != osRtxIdEventFlags) ||
      ((flags & ~(((uint32_t)1U << osRtxEventFlagsLimit) - 1U)) != 0U)) {
    EvrRtxEventFlagsError(ef, (int32_t)osErrorParameter);
    // lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return ((uint32_t)osErrorParameter);
  }

  // Clear Event Flags
  event_flags = EventFlagsClear(ef, flags);

  EvrRtxEventFlagsClearDone(ef, event_flags);

  return event_flags;
}

/// Get the current Event Flags.
/// \note API identical to osEventFlagsGet
static uint32_t svcRtxEventFlagsGet(osEventFlagsId_t ef_id) {
  os_event_flags_t *ef = osRtxEventFlagsId(ef_id);

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

  EvrRtxEventFlagsGet(ef, ef->event_flags);

  return ef->event_flags;
}

/// Wait for one or more Event Flags to become signaled.
/// \note API identical to osEventFlagsWait
static uint32_t svcRtxEventFlagsWait(osEventFlagsId_t ef_id, uint32_t flags,
                                     uint32_t options, uint32_t timeout) {
  os_event_flags_t *ef = osRtxEventFlagsId(ef_id);
  os_thread_t *thread;
  uint32_t event_flags;

  // Check parameters
  if ((ef == NULL) || (ef->id != osRtxIdEventFlags) ||
      ((flags & ~(((uint32_t)1U << osRtxEventFlagsLimit) - 1U)) != 0U)) {
    EvrRtxEventFlagsError(ef, (int32_t)osErrorParameter);
    // lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return ((uint32_t)osErrorParameter);
  }

  // Check Event Flags
  event_flags = EventFlagsCheck(ef, flags, options);
  if (event_flags != 0U) {
    EvrRtxEventFlagsWaitCompleted(ef, flags, options, event_flags);
  } else {
    // Check if timeout is specified
    if (timeout != 0U) {
      EvrRtxEventFlagsWaitPending(ef, flags, options, timeout);
      // Suspend current Thread
      if (osRtxThreadWaitEnter(osRtxThreadWaitingEventFlags, timeout)) {
        thread = osRtxThreadGetRunning();
        osRtxThreadListPut(osRtxObject(ef), thread);
        // Store waiting flags and options
        thread->wait_flags = flags;
        thread->flags_options = (uint8_t)options;
      } else {
        EvrRtxEventFlagsWaitTimeout(ef);
      }
      event_flags = (uint32_t)osErrorTimeout;
    } else {
      EvrRtxEventFlagsWaitNotCompleted(ef, flags, options);
      event_flags = (uint32_t)osErrorResource;
    }
  }

  return event_flags;
}

/// Delete an Event Flags object.
/// \note API identical to osEventFlagsDelete
static osStatus_t svcRtxEventFlagsDelete(osEventFlagsId_t ef_id) {
  os_event_flags_t *ef = osRtxEventFlagsId(ef_id);
  os_thread_t *thread;

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

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

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

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

  EvrRtxEventFlagsDestroyed(ef);

  return osOK;
}

//  Service Calls definitions
// lint ++flb "Library Begin" [MISRA Note 11]
SVC0_1(EventFlagsNew, osEventFlagsId_t, const osEventFlagsAttr_t *)
SVC0_1(EventFlagsGetName, const char *, osEventFlagsId_t)
SVC0_2(EventFlagsSet, uint32_t, osEventFlagsId_t, uint32_t)
SVC0_2(EventFlagsClear, uint32_t, osEventFlagsId_t, uint32_t)
SVC0_1(EventFlagsGet, uint32_t, osEventFlagsId_t)
SVC0_4(EventFlagsWait, uint32_t, osEventFlagsId_t, uint32_t, uint32_t, uint32_t)
SVC0_1(EventFlagsDelete, osStatus_t, osEventFlagsId_t)
// lint --flb "Library End"

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

/// Set the specified Event Flags.
/// \note API identical to osEventFlagsSet
__STATIC_INLINE
uint32_t isrRtxEventFlagsSet(osEventFlagsId_t ef_id, uint32_t flags) {
  os_event_flags_t *ef = osRtxEventFlagsId(ef_id);
  uint32_t event_flags;

  // Check parameters
  if ((ef == NULL) || (ef->id != osRtxIdEventFlags) ||
      ((flags & ~(((uint32_t)1U << osRtxEventFlagsLimit) - 1U)) != 0U)) {
    EvrRtxEventFlagsError(ef, (int32_t)osErrorParameter);
    // lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return ((uint32_t)osErrorParameter);
  }

  // Set Event Flags
  event_flags = EventFlagsSet(ef, flags);

  // Register post ISR processing
  osRtxPostProcess(osRtxObject(ef));

  EvrRtxEventFlagsSetDone(ef, event_flags);

  return event_flags;
}

/// Wait for one or more Event Flags to become signaled.
/// \note API identical to osEventFlagsWait
__STATIC_INLINE
uint32_t isrRtxEventFlagsWait(osEventFlagsId_t ef_id, uint32_t flags,
                              uint32_t options, uint32_t timeout) {
  os_event_flags_t *ef = osRtxEventFlagsId(ef_id);
  uint32_t event_flags;

  // Check parameters
  if ((ef == NULL) || (ef->id != osRtxIdEventFlags) || (timeout != 0U) ||
      ((flags & ~(((uint32_t)1U << osRtxEventFlagsLimit) - 1U)) != 0U)) {
    EvrRtxEventFlagsError(ef, (int32_t)osErrorParameter);
    // lint -e{904} "Return statement before end of function" [MISRA Note 1]
    return ((uint32_t)osErrorParameter);
  }

  // Check Event Flags
  event_flags = EventFlagsCheck(ef, flags, options);
  if (event_flags != 0U) {
    EvrRtxEventFlagsWaitCompleted(ef, flags, options, event_flags);
  } else {
    EvrRtxEventFlagsWaitNotCompleted(ef, flags, options);
    event_flags = (uint32_t)osErrorResource;
  }

  return event_flags;
}

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

/// Create and Initialize an Event Flags object.
osEventFlagsId_t osEventFlagsNew(const osEventFlagsAttr_t *attr) {
  osEventFlagsId_t ef_id;

  EvrRtxEventFlagsNew(attr);
  if (IsIrqMode() || IsIrqMasked()) {
    EvrRtxEventFlagsError(NULL, (int32_t)osErrorISR);
    ef_id = NULL;
  } else {
    ef_id = __svcEventFlagsNew(attr);
  }
  return ef_id;
}

/// Get name of an Event Flags object.
const char *osEventFlagsGetName(osEventFlagsId_t ef_id) {
  const char *name;

  if (IsIrqMode() || IsIrqMasked()) {
    EvrRtxEventFlagsGetName(ef_id, NULL);
    name = NULL;
  } else {
    name = __svcEventFlagsGetName(ef_id);
  }
  return name;
}

/// Set the specified Event Flags.
uint32_t osEventFlagsSet(osEventFlagsId_t ef_id, uint32_t flags) {
  uint32_t event_flags;

  EvrRtxEventFlagsSet(ef_id, flags);
  if (IsIrqMode() || IsIrqMasked()) {
    event_flags = isrRtxEventFlagsSet(ef_id, flags);
  } else {
    event_flags = __svcEventFlagsSet(ef_id, flags);
  }
  return event_flags;
}

/// Clear the specified Event Flags.
uint32_t osEventFlagsClear(osEventFlagsId_t ef_id, uint32_t flags) {
  uint32_t event_flags;

  EvrRtxEventFlagsClear(ef_id, flags);
  if (IsIrqMode() || IsIrqMasked()) {
    event_flags = svcRtxEventFlagsClear(ef_id, flags);
  } else {
    event_flags = __svcEventFlagsClear(ef_id, flags);
  }
  return event_flags;
}

/// Get the current Event Flags.
uint32_t osEventFlagsGet(osEventFlagsId_t ef_id) {
  uint32_t event_flags;

  if (IsIrqMode() || IsIrqMasked()) {
    event_flags = svcRtxEventFlagsGet(ef_id);
  } else {
    event_flags = __svcEventFlagsGet(ef_id);
  }
  return event_flags;
}

/// Wait for one or more Event Flags to become signaled.
uint32_t osEventFlagsWait(osEventFlagsId_t ef_id, uint32_t flags,
                          uint32_t options, uint32_t timeout) {
  uint32_t event_flags;

  EvrRtxEventFlagsWait(ef_id, flags, options, timeout);
  if (IsIrqMode() || IsIrqMasked()) {
    event_flags = isrRtxEventFlagsWait(ef_id, flags, options, timeout);
  } else {
    event_flags = __svcEventFlagsWait(ef_id, flags, options, timeout);
  }
  return event_flags;
}

/// Delete an Event Flags object.
osStatus_t osEventFlagsDelete(osEventFlagsId_t ef_id) {
  osStatus_t status;

  EvrRtxEventFlagsDelete(ef_id);
  if (IsIrqMode() || IsIrqMasked()) {
    EvrRtxEventFlagsError(ef_id, (int32_t)osErrorISR);
    status = osErrorISR;
  } else {
    status = __svcEventFlagsDelete(ef_id);
  }
  return status;
}
