/*
 * Copyright (c) 2013 - 2014, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#if !defined(__FSL_OS_ABSTRACTION_CMSIS_H__)
#define __FSL_OS_ABSTRACTION_CMSIS_H__

#include <stdint.h>
#include <stdbool.h>
#include "cmsis_os.h"

#if defined __CC_ARM
#define inline      __inline
#endif

//! @addtogroup rtos_irq_sync
//! @{

////////////////////////////////////////////////////////////////////////////////
// Declarations
////////////////////////////////////////////////////////////////////////////////

//! @brief Type for an interrupt synchronization object.
typedef volatile osSemaphoreId sync_object_t;

//! @brief Type for a resource locking object.
typedef volatile osMutexId lock_object_t;

//! @brief Type for an event group object.
typedef osThreadId event_object_t;

//! @brief Type for an event flags group. Bit 32 is reserved.
typedef int32_t event_group_t

//! @brief Type for a task pointer.
typedef void * task_t;

//! @brief Type for a task handler, returned by the task_create function.
typedef osThreadId task_handler_t;

//! @brief Type for a message queue declaration and creation.
typedef const osMessageQDef_t *  msg_queue_t;

//! @brief Type for a message queue declaration and creation.
typedef osMessageQId  msg_queue_handler_t;

//! @brief Type for a message queue item.
typedef void*  msg_queue_item_t;

//! @brief Status values to be returned by functions.
typedef enum
{
    kSuccess = 0,
    kError,
    kTimeout
}fsl_rtos_status;

enum sync_timeouts
{
    //! @brief Constant to pass for the sync_wait() timeout in order to wait indefinitely.
    kSyncWaitForever = osWaitForever
};

//! @brief Macro passed to the task_destroy function to destroy the current task.
#define FSL_RTOS_CURRENT_TASK   (osThreadGetId)

////////////////////////////////////////////////////////////////////////////////
// API
////////////////////////////////////////////////////////////////////////////////

#if defined(__cplusplus)
extern "C" {
#endif

//! @name Interrupt handler synchronization
//@{

/*!
 * @brief Create the synchronization object. To be used instead of a standard
 *      declaration.
 *
 * @param obj The sync object to create.
 */
#define sync_object_declare(obj)    osSemaphoreDef(obj); \
                                    osSemaphoreId obj

/*!
 * @brief Initialize a synchronization object to a given state.
 *
 * @param obj The sync object to initialize.
 */
#define sync_create(obj)            obj = osSemaphoreCreate(osSemaphore(obj), 0)

/*!
 * @brief Wait for a synchronization object to be signalled.
 *
 * @param obj Pointer to the synchronization object.
 * @param timeout The maximum number of milliseconds to wait for the object to be signalled.
 *      Pass the #kSyncWaitForever constant to wait indefinitely for someone to signal the object.
 *      0 should not be passed to this function. Instead, use sync_poll for a non blocking check.
 *
 * @retval kSuccess The object was signalled.
 * @retval kTimeout A timeout occurred.
 * @retval kError An incorrect parameter was passed.
 */
static inline fsl_rtos_status sync_wait(sync_object_t *obj, uint32_t timeout)
{
    int32_t retVal;

    retVal = osSemaphoreWait(*obj, timeout);

    /* Return value is -1 for error, 0 for timeout or the semaphore count before obtaining it */
    if(retVal > 0)
    {
        return kSuccess;
    }
    else if(retVal == 0)
    {
        return kTimeout;
    }
    else
    {
        return kError;
    }
}

/*!
 * @brief Checks if a synchronization object has been signalled. This function returns instantly.
 *
 * @param obj Pointer to the synchronization object.
 *
 * @retval kSuccess The object was signalled.
 * @retval kTimeout The object was not signalled.
 * @retval kError An incorrect parameter was passed.
 */
static inline fsl_rtos_status sync_poll(sync_object_t *obj)
{
    int32_t retVal;

    retVal = osSemaphoreWait(*obj, 0);

    /* Return value is -1 for error, 0 for timeout or the semaphore count before obtaining it */
    if(retVal > 0)
    {
        return kSuccess;
    }
    else if(retVal == 0)
    {
        return kTimeout;
    }
    else
    {
        return kError;
    }
}

/*!
 * @brief Signal for someone waiting on the synchronization object to wake up. This function
 *      should not be called from an interrupt. Use sync_signal_from_isr instead.
 *
 * @param obj Pointer to the synchronization object to signal.
 *
 * @retval OK The object was successfully signalled.
 */
static inline fsl_rtos_status sync_signal(sync_object_t *obj)
{
    return (fsl_rtos_status)osSemaphoreRelease(*obj);
}

/*!
 * @brief Signal from an ISR for someone waiting on the synchronization object to wake up.
 *
 * @param obj The synchronization object to signal.
 *
 * @retval OK The object was successfully signalled.
 */
#define sync_signal_from_isr        sync_signal     /* Same as non ISR */

/*!
 * @brief Destroy a previously created synchronization object.
 *
 * @param obj The synchronization object to destroy.
 *
 * @retval OK The object was successfully destroyed.
 */
static inline fsl_rtos_status sync_destroy(sync_object_t *obj)
{
    return (fsl_rtos_status)osSemaphoreDelete(*obj);
}

//@}

//! @name Resource locking
//@{

/*!
 * @brief Create the locking object. To be used instead of a standard
 *      declaration.
 *
 * @param obj The lock object to create.
 */
#define lock_object_declare(obj)    osMutexDef(obj); \
                                    osMutexId obj

/*!
 * @brief Initialize a locking object to a given state.
 *
 * @param obj The lock object to initialize.
 */
#define lock_create(obj)            obj = osMutexCreate(osMutex(obj))

/*!
 * @brief Wait for the object to be unlocked and lock it.
 *
 * @param obj Pointer to the locking object.
 * @param timeout The maximum number of milliseconds to wait for the mutex.
 *      Pass the #kSyncWaitForever constant to wait indefinitely for someone to signal the object.
 *      0 should not be passed to this function. Instead, use sync_poll for a non blocking check.
 *
 * @retval kSuccess The lock was obtained.
 * @retval kTimeout A timeout occurred.
 * @retval kError An incorrect parameter was passed.
 */
static inline fsl_rtos_status lock_wait(lock_object_t *obj, uint32_t timeout)
{
    osStatus retVal;

    retVal = osMutexWait(*obj, timeout);

    if(retVal == osOK)
    {
        return kSuccess;
    }
    else if(retVal == osErrorTimeoutResource)
    {
        return kTimeout;
    }
    else
    {
        return kError;
    }
}

/*!
 * @brief Checks if a locking object can be locked and locks it if possible.
 *      This function returns instantly.
 *
 * @param obj Pointer to the locking object.
 *
 * @retval kSuccess The lock was obtained.
 * @retval kTimeout The lock was not available.
 * @retval kError An incorrect parameter was passed.
 */
static inline fsl_rtos_status lock_poll(lock_object_t *obj)
{
    osStatus retVal;

    retVal = osMutexWait(*obj, 0);

    if(retVal == osOK)
    {
        return kSuccess;
    }
    else if(retVal == osErrorResource)
    {
        return kTimeout;
    }
    else
    {
        return kError;
    }
}

/*!
 * @brief Unlock a previously locked object.
 *
 * @param obj The locking object to unlock.
 *
 * @retval OK The object was successfully unlocked.
 */
static inline fsl_rtos_status lock_release(lock_object_t *obj)
{
    return osMutexRelease(*obj);
}

/*!
 * @brief Destroy a previously created locking object.
 *
 * @param obj The locking object to destroy.
 *
 * @retval OK The object was successfully destroyed.
 */
static inline fsl_rtos_status lock_destroy(lock_object_t *obj)
{
    return osMutexDelete(*obj);
}

//@}

//! @name Event signaling
//@{

/*!
 * @brief Initializes the event object to a known state.
 *
 * @param obj Pointer to the event object to initialize.
 */
static inline void event_create(event_object_t *obj)
{
    /* Nothing to be done. Events are signaled to a specific thread, and the waiting thread is stored
     * at the event_wait function.
     */
    
    (void)obj;  /* To avoid compiler warning */
}

/*!
 * @brief Wait for any event to be set.
 *
 * @param obj The event object.
 * @param timeout The maximum number of milliseconds to wait for the event.
 *      Pass the #kSyncWaitForever constant to wait indefinitely. 0 should not be passed 
 *      to this function.
 * @param setFlags Pointer to receive the flags that were set.
 *
 * @retval kSuccess An event was set.
 * @retval kTimeout A timeout occurred.
 * @retval kError An incorrect parameter was passed.
 */
static inline fsl_rtos_status event_wait(event_object_t *obj, uint32_t timeout, event_group_t *setFlags)
{
    osEvent retVal;
    
    obj = osThreadGetId();
    retVal = osSignalWait(0, timeout);    /* Wait for any flag */
    
    if(retVal.status == osEventSignal)
    {
        *setFlags = retVal.value.signals;
        
        return kSuccess;
    }
    else if(retVal.status == osEventTimeout)
    {
        return kTimeout;
    }
    else
    {
        return kError;
    }
}

/*!
 * @brief Set one or more events of an event object. This function should not be called from an ISR.
 *
 * @param obj The event object.
 * @param setFlags Event flags to be set.
 *
 * @retval kSuccess The flags were successfully set.
 * @retval kError An incorrect parameter was passed.
 */
static inline fsl_rtos_status event_set(event_object_t *obj, event_group_t flags)
{
    int32_t retVal;
    
    retVal = osSignalSet(*obj, flags);
    
    if(retVal >= 0)     /* 0x80000000 equals error */
    {
        return kSuccess;
    }
    else
    {
        return kError;
    }
}

/*!
 * @brief Set one or more events of an event object. This function should only be called from an ISR.
 *
 * @param obj The event object.
 * @param setFlags Event flags to be set.
 *
 * @retval kSuccess The flags were successfully set.
 * @retval kError An incorrect parameter was passed.
 */
static inline fsl_rtos_status event_set_from_isr(event_object_t *obj, event_group_t flags)
{
    return event_set(obj, flags);
}

/*!
 * @brief Destroy a previously created event object.
 *
 * @param obj The event object to destroy.
 * 
 * @retval kSuccess The object was successfully destroyed.
 */
static inline fsl_rtos_status event_destroy(event_object_t *obj)
{
    *obj = NULL;
    
    return kSuccess;
}

//@}

//! @name Thread management
//@{
/*!
 * @brief Creates a task descriptor that is used to create the task with task_create.
 *
 * @param task The task function.
 * @param stackSize Number of elements in the stack for this task.
 * @param name String to assign to the task.
 * @param usesFloat Boolean that indicates whether the task uses the floating point unit.
 */
#define FSL_RTOS_TASK_DEFINE(task, stackSize, name, usesFloat) static const uint16_t fslTaskStackSize_##task = stackSize 

/*!
 * @brief Creates and sets the task to active.
 *
 * @param task The task function.
 * @param priority Initial priority of the task.
 * @param param Pointer to be passed to the task when it is created.
 * @param handler Returns the identifier to be used afterwards to destroy the task.
 * 
 * @retval kSuccess The task was successfully created.
 */
#define task_create(task, priority, param, handler)   __task_create(task, \
                                                                    NULL, \
                                                                    fslTaskStackSize_##task, \
                                                                    NULL, \
                                                                    priority, \
                                                                    param, \
                                                                    NULL, \
                                                                    handler)  

/* Internal function that receives the all parameters required for task creation */
static inline fsl_rtos_status __task_create(task_t task, uint8_t *name, uint16_t stackSize, task_stack_t *stackMem,
                                            uint16_t priority, void *param, bool usesFloat, task_handler_t *handler)
{
    (void)name;
    (void)stackMem;
    (void)usesFloat;
    
    osThreadDef(task, priority, 1, stackSize);
    
    *handler = osThreadCreate (osThread(task), param);
    
    if(*handler != NULL)
    {
        return kSuccess;
    }
    else
    {
        return kError;
    }
}

/*!
 * @brief Destroy a previously created task.
 * @note Depending on the RTOS, task resources may or may not be automatically freed, and this function may
 *       not return if the current task is destroyed.
 *
 * @param handler The handler of the task to destroy. Returned by the task_create function.
 * 
 * @retval kSuccess The task was successfully destroyed.
 */
static inline fsl_rtos_status task_destroy(task_handler_t handler)
{
    return (fsl_rtos_status)osThreadTerminate(handler);
}

//@}

//! @name Message queues
//@{

/*!
 * @brief This macro statically reserves the memory required for the queue.
 * @note The queue will store pointers to the elements, and no the elements themselves. The
 *       element must continue to exist in memory for the receiving end to properly get the
 *       contents.
 *
 * @param name Identifier for the memory region.
 * @param size Number of elements in the queue. 
 */
#define MSG_QUEUE_DECLARE(name, size)   osMessageQDef(name, size, void*)

/*!
 * @brief Initialize the message queue.
 *
 * @param queue The queue declared through the MSG_QUEUE_DECLARE macro.
 * @param size The number of elements in the queue.
 * 
 * @retval Handler to access the queue for put and get operations.
 */
#define msg_queue_create(queue, size)       msg_queue_create_cmsis(osMessageQ(queue), size)

static inline msg_queue_handler_t msg_queue_create_cmsis(msg_queue_t queue, uint16_t size)
{
    /* Create the message queue where each element is a pointer to the message item. */
    return osMessageCreate(queue, NULL);
}

/*!
 * @brief Introduce an element at the tail of the queue.
 *
 * @param handler Queue handler returned by the msg_queue_create function.
 * @param item Pointer to the element to be introduced in the queue.
 * 
 * @retval kSuccess Element successfully introduced in the queue.
 */
static inline fsl_rtos_status msg_queue_put(msg_queue_handler_t handler, msg_queue_item_t item)
{
    /* In this case, the message is passed by reference, so passing the pointer is ok. */
    return (fsl_rtos_status)osMessagePut(handler, (uint32_t)item, 0);     /* Does not block if full */
}

/*!
 * @brief Read and remove an element at the head of the queue.
 *
 * @param handler Queue handler returned by the msg_queue_create function.
 * @param item Pointer to store a pointer to the element of the queue.
 * @param timeout In case the queue is empty, the number of milliseconds to
 *        wait for an element to be introduced into the queue. Use 0 to return
 *        immediately or #kSyncWaitForever to wait indefinitely.
 * 
 * @retval kSuccess Element successfully obtained from the queue.
 * @retval kTimeout If a timeout was specified, the queue remained empty after timeout.
 * @retval kError The queue was empty or the handler was invalid.
 */
static inline fsl_rtos_status msg_queue_get(msg_queue_handler_t handler, msg_queue_item_t *item, uint32_t timeout)
{
    osEvent retVal;
    
    retVal = osMessageGet(handler, timeout);
    
    if(retVal.status == osEventMessage)
    {
        *item = (msg_queue_item_t)retVal.value.p;
        
        return kSuccess;
    }
    else if(retVal.status == osEventTimeout)
    {
        return kTimeout;
    }
    else
    {
        return kError;
    }
}

/*!
 * @brief Discards all elements in the queue and leaves the queue empty.
 *
 * @param handler Queue handler returned by the msg_queue_create function.
 * 
 * @retval kSuccess Queue successfully emptied.
 */
static inline fsl_rtos_status msg_queue_flush(msg_queue_handler_t handler)
{
    osEvent retVal;
    
    do
    {
        retVal = osMessageGet(handler, 0);     /* Does not block if empty */
    }while(retVal.status == osEventMessage);
    
    /* If last status indicates the queue is empty, then the operation succeeded. Return erro otherwise. */
    if(retVal.status == osOK)
    {
        return kSuccess;
    }
    else
    {
        return kError;
    }
}

/*!
 * @brief Destroy a previously created queue.
 *
 * @param handler Queue handler returned by the msg_queue_create function.
 * 
 * @retval kSuccess The queue was successfully destroyed.
 */
static inline fsl_rtos_status msg_queue_destroy(msg_queue_handler_t handler)
{
    /* CMSIS RTOS API does not have a function to detroy/deinit the msg queue. */
    return kSuccess;
}

//@}

//! @name Memory Management
//@{

/*!
 * @brief Reserves the requested amount of memory in bytes.
 *
 * @param size Amount of bytes to reserve. 
 * 
 * @retval Pointer to the reserved memory. NULL if memory could not be allocated.
 */
static inline void * mem_allocate(size_t size)
{
    return malloc(size);
}

/*!
 * @brief Reserves the requested amount of memory in bytes and initializes it to 0.
 *
 * @param size Amount of bytes to reserve. 
 * 
 * @retval Pointer to the reserved memory. NULL if memory could not be allocated.
 */
static inline void * mem_allocate_zero(size_t size)
{
    return calloc(size);
}

/*!
 * @brief Releases the memory previously reserved.
 *
 * @param ptr Pointer to the start of the memory block previously reserved. 
 * 
 * @retval kSuccess Memory correctly released.
 */
static inline fsl_rtos_status mem_free(void *ptr)
{
    free(ptr);
    
    return kSuccess;
}

//@}

//! @name Critical Sections
//@{

//! @brief Ensures the following code will not be preempted. Required attention to other system events may be delayed.
static inline void rtos_enter_critical(void)
{
    /* Nothing found on CMSIS RTOS API to meet this functionality. */
    __disable_irq();
}

//! @brief Allows preemption
static inline void rtos_exit_critical(void)
{
    /* Nothing found on CMSIS RTOS API to meet this functionality. */
    __enable_irq();
}

//@}

//! @name Time management
//@{

/*!
 * @brief Delays execution for a number of milliseconds.
 *
 * @param delay The time in milliseconds to wait.
 */
static inline void time_delay(uint32_t delay)
{
    (void)osDelay(delay);
}

//@}

#if defined(__cplusplus)
}
#endif

//! @}

#endif // __FSL_OS_ABSTRACTION_CMSIS_H__
////////////////////////////////////////////////////////////////////////////////
// EOF
////////////////////////////////////////////////////////////////////////////////

