/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* This program is free software; you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation, using version 2 of the License.             */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/
/**
    @file   sem.c
    @brief  Linux os abstraction layer for semaphore handling

    @b  Module:  PHSTBOSAL Operating System Abstraction Layer

*//*

    This is the Linux implementation of the semaphore handling Osal



Rev Date        Author      Comments
--------------------------------------------------------------------------------
008 20080108    barnish     make tmosalSemAcquire() interruptable
8.1.1 20080205  batelaan    Update file copyright/license text
009 20080201    steel       make tmosalSemAcquire() uninterruptable by harmless signals
010 20080211    burningh    Merge 8.1.1 and 9
011 20080220    barnish     remove interruptable sem feature
012 20080326    mpage       Conditionally enabling TimeDoctor events
--------------------------------------------------------------------------------

    For consistency and standardisation retain the Section Separators.
*/

#include "common.h"

#if defined (CONFIG_TIME_DOCTOR)
#include "linux/timedoctor.h"
#endif

/* Internal OSAL semaphore structure */
struct osal_sem {
    int value;
    wait_queue_head_t wait;
    int dead;           /* if non-zero, semaphore was destroyed */
};

static struct handle_table sem_handle_table;
#define allocate_osal_sem_handle(handle) \
    ALLOCATE_HANDLE_MACRO(&sem_handle_table, handle, TMOSAL_ERR_SEM_FULL)
#define allocate_osal_sem(os) \
    ALLOCATE_ZEROED_MACRO(os)
#define bind_osal_sem_to_handle(handle, os) \
    BIND_OBJECT_MACRO(&sem_handle_table, handle, os)
#define cancel_osal_sem_handle(handle) \
    CANCEL_HANDLE_MACRO(&sem_handle_table, handle)
#define get_osal_sem(handle, os) \
    GET_OBJECT_MACRO(&sem_handle_table, handle, os, TMOSAL_ERR_HANDLE)
#define put_osal_sem(handle) \
    PUT_OBJECT_MACRO(&sem_handle_table, handle)
#define mark_dead_osal_sem(handle) \
    MARK_DEAD_OBJECT_MACRO(&sem_handle_table, handle)


/* Semaphore API */

tmErrorCode_t tmosalSemCreate(
        const UInt32 kCount,
        ptmosalSemHandle_t pioSemHandle,
        const tmosalSemCreateFlags_t kFlags)
{
    u32 handle;
    struct osal_sem *os;
    
    BUG_IF_INTERRUPT();
    BUG_IF_NOT_PREEMPTIBLE();
    
    if (unlikely(kCount <= 0) || unlikely(!pioSemHandle))
        return TMOSAL_ERR_BAD_PARAM;

    allocate_osal_sem_handle(handle);
    allocate_osal_sem(os);
    if (unlikely(!os)) {
        cancel_osal_sem_handle(handle);
        return TMOSAL_ERR_OUT_OF_MEMORY;
    }

    os->value = kCount;
    init_waitqueue_head(&os->wait);

    STAT_HANDLE_CREATE(curSemaphoreNr, maxSemaphoreNr);
    bind_osal_sem_to_handle(handle, os);
    *pioSemHandle = handle;
    return TM_OK;
}
EXPORT_SYMBOL(tmosalSemCreate);


tmErrorCode_t tmosalSemDestroy(const tmosalSemHandle_t kSemHandle)
{
    struct osal_sem *os;
    unsigned long flags;
    int empty;
    tmErrorCode_t ret;
    
    get_osal_sem(kSemHandle, os);
    spin_lock_irqsave(&os->wait.lock, flags);
    os->dead = 1;
    empty = list_empty(&os->wait.task_list);
    spin_unlock_irqrestore(&os->wait.lock, flags);
    
    if (likely(empty))
        ret = TM_OK;
    else {
        wake_up(&os->wait);
        if (!in_interrupt() && preemptible())
            schedule();
        ret = TMOSAL_ERR_SEM_TASK;
    }

    STAT_HANDLE_DESTROY(curSemaphoreNr);
    mark_dead_osal_sem(kSemHandle);
    put_osal_sem(kSemHandle);
    return ret;
}
EXPORT_SYMBOL(tmosalSemDestroy);


tmErrorCode_t tmosalSemAcquire(const tmosalSemHandle_t kSemHandle,
        const pInt32 pkTimeout)
{
    struct osal_sem *os;
    unsigned long flags;
    int jif;
    bool exit = false;
    tmErrorCode_t ret = TM_OK;
    DECLARE_WAITQUEUE(wait, current);
            
    BUG_IF_SLEEP_IN_ATOMIC(pkTimeout);

    get_osal_sem(kSemHandle, os);

    jif = (pkTimeout && *pkTimeout > 0) ? msecs_to_jiffies(*pkTimeout) : 0;
    
    spin_lock_irqsave(&os->wait.lock, flags);
    
    wait.flags = 0;     /* non-exclusive */
    __add_wait_queue(&os->wait, &wait);

    while (exit == false) {

        /* check for dead */
        if (unlikely(os->dead)) {
            ret = TMOSAL_ERR_SEM_DEL;
            exit = true;
        }

        /* check for acquire condition */
        else if (likely(os->value > 0)) {
            os->value--;
            ret = TM_OK;
            exit = true;
        }

        /* check for timeout condition */
        else if (pkTimeout && !jif) {
            ret = TMOSAL_ERR_SEM_TIMEOUT;
            exit = true;
        }

        if (exit == false)
        {
#if defined (CONFIG_TIME_DOCTOR)
            if (osal_debug_level > 0)
            {
                char name[9];
                sprintf(name, "%07XS", (unsigned int)kSemHandle);
                timeDoctor_Info(TDI_COMMAND(TDI_START, TDI_SEM),
                            CHAR2_ARRAY_TO_U32(name), CHAR_ARRAY_TO_U32(name));
            }
#endif

            /* will sleep */
            set_current_state(TASK_INTERRUPTIBLE);
            spin_unlock_irqrestore(&os->wait.lock, flags);
            if (pkTimeout)
            {
                jif = schedule_timeout(jif);
            }
            else
            {
                schedule();
            }
            spin_lock_irqsave(&os->wait.lock, flags);

            /* will check the wakeup reason on new iteration */
        }
    }

    __remove_wait_queue(&os->wait, &wait);
    spin_unlock_irqrestore(&os->wait.lock, flags);
    put_osal_sem(kSemHandle);

    return ret;
}
EXPORT_SYMBOL(tmosalSemAcquire);


tmErrorCode_t tmosalSemRelease(const tmosalSemHandle_t kSemHandle)
{
    struct osal_sem *os;
    unsigned long flags;
    int waiter_exists;
    
    get_osal_sem(kSemHandle, os);
    spin_lock_irqsave(&os->wait.lock, flags);
    os->value++;
    waiter_exists = !list_empty(&os->wait.task_list);
    spin_unlock_irqrestore(&os->wait.lock, flags);

#if defined (CONFIG_TIME_DOCTOR)
    if (osal_debug_level > 0)
    {
        char name[9];
        sprintf(name, "%07XS", (unsigned int)kSemHandle);
        timeDoctor_Info(TDI_COMMAND(TDI_STOP, TDI_SEM),
                    CHAR_ARRAY_TO_U32((name+4)), CHAR_ARRAY_TO_U32(name));
    }
#endif

    if (unlikely(waiter_exists)) {
        /* it could be better to use wake_up_locked() here, to avoid
           races. Unfortunately, it is not exported. But there is no
           much harm anyway, because extra call to wake_up() will just
           wakeup threads, which will check situation and go sleep
           again if needed */
        wake_up(&os->wait);
        if (preemptible() && !in_interrupt())
            schedule();
    }

    put_osal_sem(kSemHandle);
    return TM_OK;
}
EXPORT_SYMBOL(tmosalSemRelease);


int osal_sem_submodule_init(void)
{
    return init_handle_table(&sem_handle_table, SEM_HANDLE_TABLE_INITIAL_SIZE, "semaphore");
}

void osal_sem_submodule_uninit(void)
{
    uninit_handle_table(&sem_handle_table);
}
