/* $Id: os_core_linux_kernel.c 2394 2008-12-23 17:27:53Z bennylp $ */
/* 
 * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 
 */
#include <pj/os.h>
#include <pj/assert.h>
#include <pj/pool.h>
#include <pj/log.h>
#include <pj/except.h>
#include <pj/errno.h>
#include <pj/string.h>
#include <pj/compat/high_precision.h>
#include <pj/compat/sprintf.h>

#include <linux/config.h>
#include <linux/version.h>
#if defined(MODVERSIONS)
#include <linux/modversions.h>
#endif
#include <linux/kernel.h>
#include <linux/sched.h>
//#include <linux/tqueue.h>
#include <linux/wait.h>
#include <linux/signal.h>

#include <asm/atomic.h>
#include <asm/unistd.h>
#include <asm/semaphore.h>

#define THIS_FILE   "oslinuxkern"

struct pj_thread_t
{
    /** Thread's name. */
    char obj_name[PJ_MAX_OBJ_NAME];

    /** Linux task structure for thread. */
    struct task_struct *thread;	

    /** Flags (specified in pj_thread_create) */
    unsigned flags;

    /** Task queue needed to launch thread. */
    //struct tq_struct	tq;	

    /** Semaphore needed to control thread startup. */
    struct semaphore	startstop_sem;

    /** Semaphore to suspend thread during startup. */
    struct semaphore	suspend_sem;

    /** Queue thread is waiting on. Gets initialized by
        thread_initialize, can be used by thread itself.
     */
    wait_queue_head_t	queue;

    /** Flag to tell thread whether to die or not.
        When the thread receives a signal, it must check
        the value of terminate and call thread_deinitialize and terminate
        if set.
     */
    int terminate;    

    /** Thread's entry. */
    pj_thread_proc *func;

    /** Argument. */
    void *arg;
};

struct pj_atomic_t
{
    atomic_t atom;
};

struct pj_mutex_t
{
    struct semaphore sem;
    pj_bool_t	     recursive;
    pj_thread_t	    *owner;
    int		     own_count;
};

struct pj_sem_t
{
    struct semaphore sem;
};

/*
 * Static global variables.
 */
#define MAX_TLS_ID  32
static void *tls_values[MAX_TLS_ID];
static int tls_id;
static long thread_tls_id;
static spinlock_t critical_section = SPIN_LOCK_UNLOCKED;
static unsigned long spinlock_flags;
static pj_thread_t main_thread;

/* private functions */
//#define TRACE_(expr)	PJ_LOG(3,expr)
#define TRACE_(x)


/* This must be called in the context of the new thread. */
static void thread_initialize( pj_thread_t *thread )
{
    TRACE_((THIS_FILE, "---new thread initializing..."));

    /* Set TLS */
    pj_thread_local_set(thread_tls_id, thread);

    /* fill in thread structure */
    thread->thread = current;
    pj_assert(thread->thread != NULL);

    /* set signal mask to what we want to respond */
    siginitsetinv(&current->blocked, 
		  sigmask(SIGKILL)|sigmask(SIGINT)|sigmask(SIGTERM));

    /* initialise wait queue */
    init_waitqueue_head(&thread->queue);

    /* initialise termination flag */
    thread->terminate = 0;

    /* set name of this process (making sure obj_name is null 
     * terminated first) 
     */
    thread->obj_name[PJ_MAX_OBJ_NAME-1] = '\0';
    sprintf(current->comm, thread->obj_name);
        
    /* tell the creator that we are ready and let him continue */
    up(&thread->startstop_sem);	
}

/* cleanup of thread. Called by the exiting thread. */
static void thread_deinitialize(pj_thread_t *thread)
{
    /* we are terminating */

    /* lock the kernel, the exit will unlock it */
    thread->thread = NULL;
    mb();

    /* notify the stop_kthread() routine that we are terminating. */
    up(&thread->startstop_sem);

    /* the kernel_thread that called clone() does a do_exit here. */

    /* there is no race here between execution of the "killer" and 
       real termination of the thread (race window between up and do_exit), 
       since both the thread and the "killer" function are running with 
       the kernel lock held.
       The kernel lock will be freed after the thread exited, so the code
       is really not executed anymore as soon as the unload functions gets
       the kernel lock back.
       The init process may not have made the cleanup of the process here,
       but the cleanup can be done safely with the module unloaded.
    */

}

static int thread_proc(void *arg)
{
    pj_thread_t *thread = arg;

    TRACE_((THIS_FILE, "---new thread starting!"));

    /* Initialize thread. */
    thread_initialize( thread );

    /* Wait if created suspended. */
    if (thread->flags & PJ_THREAD_SUSPENDED) {
	TRACE_((THIS_FILE, "---new thread suspended..."));
	down(&thread->suspend_sem);
    }

    TRACE_((THIS_FILE, "---new thread running..."));

    pj_assert(thread->func != NULL);

    /* Call thread's entry. */
    (*thread->func)(thread->arg);

    TRACE_((THIS_FILE, "---thread exiting..."));

    /* Cleanup thread. */
    thread_deinitialize(thread);

    return 0;
}

/* The very task entry. */
static void kthread_launcher(void *arg)
{
    TRACE_((THIS_FILE, "...launching thread!..."));
    kernel_thread(&thread_proc, arg, 0);
}

PJ_DEF(pj_status_t) pj_init(void)
{
    pj_status_t rc;

    PJ_LOG(5, ("pj_init", "Initializing PJ Library.."));

    rc = pj_thread_init();
    if (rc != PJ_SUCCESS)
	return rc;

    /* Initialize exception ID for the pool. 
     * Must do so after critical section is configured.
     */
    rc = pj_exception_id_alloc("PJLIB/No memory", &PJ_NO_MEMORY_EXCEPTION);
    if (rc != PJ_SUCCESS)
        return rc;

    return PJ_SUCCESS;
}

PJ_DEF(pj_uint32_t) pj_getpid(void)
{
    return 1;
}

PJ_DEF(pj_status_t) pj_thread_register ( const char *cstr_thread_name,
					 pj_thread_desc desc,
					 pj_thread_t **ptr_thread)
{
    char stack_ptr;
    pj_thread_t *thread = (pj_thread_t *)desc;
    pj_str_t thread_name = pj_str((char*)cstr_thread_name);

    /* Size sanity check. */
    if (sizeof(pj_thread_desc) < sizeof(pj_thread_t)) {
	pj_assert(!"Not enough pj_thread_desc size!");
	return PJ_EBUG;
    }

    /* If a thread descriptor has been registered before, just return it. */
    if (pj_thread_local_get (thread_tls_id) != 0) {
	// 2006-02-26 bennylp:
	//  This wouldn't work in all cases!.
	//  If thread is created by external module (e.g. sound thread),
	//  thread may be reused while the pool used for the thread descriptor
	//  has been deleted by application.
	//*thread_ptr = (pj_thread_t*)pj_thread_local_get (thread_tls_id);
        //return PJ_SUCCESS;
    }

    /* Initialize and set the thread entry. */
    pj_bzero(desc, sizeof(struct pj_thread_t));

    if(cstr_thread_name && pj_strlen(&thread_name) < sizeof(thread->obj_name)-1)
	pj_sprintf(thread->obj_name, cstr_thread_name, thread->thread);
    else
	pj_snprintf(thread->obj_name, sizeof(thread->obj_name), 
		    "thr%p", (void*)thread->thread);
    
    /* Initialize. */
    thread_initialize(thread);

    /* Eat semaphore. */
    down(&thread->startstop_sem);

#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK!=0
    thread->stk_start = &stack_ptr;
    thread->stk_size = 0xFFFFFFFFUL;
    thread->stk_max_usage = 0;
#else
    stack_ptr = '\0';
#endif

    *ptr_thread = thread;
    return PJ_SUCCESS;
}


pj_status_t pj_thread_init(void)
{
    pj_status_t rc;
    pj_thread_t *dummy;
    
    rc = pj_thread_local_alloc(&thread_tls_id);
    if (rc != PJ_SUCCESS)
	return rc;

    return pj_thread_register("pjlib-main", (long*)&main_thread, &dummy);
}

PJ_DEF(pj_status_t) pj_thread_create( pj_pool_t *pool, const char *thread_name,
				      pj_thread_proc *proc, void *arg,
				      pj_size_t stack_size, unsigned flags,
				      pj_thread_t **ptr_thread)
{
    pj_thread_t *thread;

    TRACE_((THIS_FILE, "pj_thread_create()"));
    
    PJ_ASSERT_RETURN(pool && proc && ptr_thread, PJ_EINVAL);

    thread = pj_pool_zalloc(pool, sizeof(pj_thread_t));
    if (!thread)
	return PJ_ENOMEM;

    PJ_UNUSED_ARG(stack_size);

    /* Thread name. */
    if (!thread_name) 
	thread_name = "thr%p";
    
    if (strchr(thread_name, '%')) {
	pj_snprintf(thread->obj_name, PJ_MAX_OBJ_NAME, thread_name, thread);
    } else {
	strncpy(thread->obj_name, thread_name, PJ_MAX_OBJ_NAME);
	thread->obj_name[PJ_MAX_OBJ_NAME-1] = '\0';
    }
    
    /* Init thread's semaphore. */
    TRACE_((THIS_FILE, "...init semaphores..."));
    init_MUTEX_LOCKED(&thread->startstop_sem);
    init_MUTEX_LOCKED(&thread->suspend_sem);

    thread->flags = flags;

    if ((flags & PJ_THREAD_SUSPENDED) == 0) {
	up(&thread->suspend_sem);
    }

    /* Store the functions and argument. */
    thread->func = proc;
    thread->arg = arg;
    
    /* Save return value. */
    *ptr_thread = thread;
    
    /* Create the new thread by running a task through keventd. */

#if 0
    /* Initialize the task queue struct. */
    thread->tq.sync = 0;
    INIT_LIST_HEAD(&thread->tq.list);
    thread->tq.routine = kthread_launcher;
    thread->tq.data = thread;

    /* and schedule it for execution. */
    schedule_task(&thread->tq);
#endif
    kthread_launcher(thread);

    /* Wait until thread has reached the setup_thread routine. */
    TRACE_((THIS_FILE, "...wait for the new thread..."));
    down(&thread->startstop_sem);

    TRACE_((THIS_FILE, "...main thread resumed..."));
    return PJ_SUCCESS;
}

PJ_DEF(const char*) pj_thread_get_name(pj_thread_t *thread)
{
    return thread->obj_name;
}

PJ_DEF(pj_status_t) pj_thread_resume(pj_thread_t *thread)
{
    up(&thread->suspend_sem);
    return PJ_SUCCESS;
}

PJ_DEF(pj_thread_t*) pj_thread_this(void)
{
    return (pj_thread_t*)pj_thread_local_get(thread_tls_id);
}

PJ_DEF(pj_status_t) pj_thread_join(pj_thread_t *p)
{
    TRACE_((THIS_FILE, "pj_thread_join()"));
    down(&p->startstop_sem);
    TRACE_((THIS_FILE, "  joined!"));
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_thread_destroy(pj_thread_t *thread)
{
    PJ_ASSERT_RETURN(thread != NULL, PJ_EINVALIDOP);
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_thread_sleep(unsigned msec)
{
    pj_highprec_t ticks;
    pj_thread_t *thread = pj_thread_this();

    PJ_ASSERT_RETURN(thread != NULL, PJ_EBUG);
    
    /* Use high precision calculation to make sure we don't
     * crop values:
     *
     *	ticks = HZ * msec / 1000
     */
    ticks = HZ;
    pj_highprec_mul(ticks, msec);
    pj_highprec_div(ticks, 1000);

    TRACE_((THIS_FILE, "this thread will sleep for %u ticks", ticks));
    interruptible_sleep_on_timeout( &thread->queue, ticks);
    return PJ_SUCCESS;
}


///////////////////////////////////////////////////////////////////////////////
PJ_DEF(pj_status_t) pj_atomic_create( pj_pool_t *pool, 
				      pj_atomic_value_t value,
				      pj_atomic_t **ptr_var)
{
    pj_atomic_t *t = pj_pool_calloc(pool, 1, sizeof(pj_atomic_t));
    if (!t) return PJ_ENOMEM;

    atomic_set(&t->atom, value);
    *ptr_var = t;
    
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_atomic_destroy( pj_atomic_t *var )
{
    return PJ_SUCCESS;
}

PJ_DEF(void) pj_atomic_set(pj_atomic_t *var, pj_atomic_value_t value)
{
    atomic_set(&var->atom, value);
}

PJ_DEF(pj_atomic_value_t) pj_atomic_get(pj_atomic_t *var)
{
    return atomic_read(&var->atom);
}

PJ_DEF(void) pj_atomic_inc(pj_atomic_t *var)
{
    atomic_inc(&var->atom);
}

PJ_DEF(void) pj_atomic_dec(pj_atomic_t *var)
{
    atomic_dec(&var->atom);
}

PJ_DEF(void) pj_atomic_add( pj_atomic_t *var, pj_atomic_value_t value )
{
    atomic_add(value, &var->atom);
}


///////////////////////////////////////////////////////////////////////////////
PJ_DEF(pj_status_t) pj_thread_local_alloc(long *index)
{
    if (tls_id >= MAX_TLS_ID)
	return PJ_ETOOMANY;
    
    *index = tls_id++;

    return PJ_SUCCESS;
}

PJ_DEF(void) pj_thread_local_free(long index)
{
    pj_assert(index >= 0 && index < MAX_TLS_ID);
}

PJ_DEF(pj_status_t) pj_thread_local_set(long index, void *value)
{
    pj_assert(index >= 0 && index < MAX_TLS_ID);
    tls_values[index] = value;
    return PJ_SUCCESS;
}

PJ_DEF(void*) pj_thread_local_get(long index)
{
    pj_assert(index >= 0 && index < MAX_TLS_ID);
    return tls_values[index];
}


///////////////////////////////////////////////////////////////////////////////
PJ_DEF(void) pj_enter_critical_section(void)
{
    spin_lock_irqsave(&critical_section, spinlock_flags);
}

PJ_DEF(void) pj_leave_critical_section(void)
{
    spin_unlock_irqrestore(&critical_section, spinlock_flags);
}


///////////////////////////////////////////////////////////////////////////////
PJ_DEF(pj_status_t) pj_mutex_create( pj_pool_t *pool, 
				     const char *name, 
				     int type,
				     pj_mutex_t **ptr_mutex)
{
    pj_mutex_t *mutex;
    
    PJ_UNUSED_ARG(name);

    mutex = pj_pool_alloc(pool, sizeof(pj_mutex_t));
    if (!mutex)
	return PJ_ENOMEM;

    init_MUTEX(&mutex->sem);

    mutex->recursive = (type == PJ_MUTEX_RECURSE);
    mutex->owner = NULL;
    mutex->own_count = 0;
    
    /* Done. */
    *ptr_mutex = mutex;
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_mutex_create_simple( pj_pool_t *pool, const char *name,
					    pj_mutex_t **mutex )
{
    return pj_mutex_create(pool, name, PJ_MUTEX_SIMPLE, mutex);
}

PJ_DEF(pj_status_t) pj_mutex_create_recursive( pj_pool_t *pool,
					       const char *name,
					       pj_mutex_t **mutex )
{
    return pj_mutex_create( pool, name, PJ_MUTEX_RECURSE, mutex);
}

PJ_DEF(pj_status_t) pj_mutex_lock(pj_mutex_t *mutex)
{
    PJ_ASSERT_RETURN(mutex, PJ_EINVAL);

    if (mutex->recursive) {
	pj_thread_t *this_thread = pj_thread_this();
	if (mutex->owner == this_thread) {
	    ++mutex->own_count;
	} else {
	    down(&mutex->sem);
	    pj_assert(mutex->own_count == 0);
	    mutex->owner = this_thread;
	    mutex->own_count = 1;
	}
    } else {
	down(&mutex->sem);
    }
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_mutex_trylock(pj_mutex_t *mutex)
{
    long rc;

    PJ_ASSERT_RETURN(mutex, PJ_EINVAL);

    if (mutex->recursive) {
	pj_thread_t *this_thread = pj_thread_this();
	if (mutex->owner == this_thread) {
	    ++mutex->own_count;
	} else {
	    rc = down_interruptible(&mutex->sem);
	    if (rc != 0)
		return PJ_RETURN_OS_ERROR(-rc);
	    pj_assert(mutex->own_count == 0);
	    mutex->owner = this_thread;
	    mutex->own_count = 1;
	}
    } else {
	int rc = down_trylock(&mutex->sem);
	if (rc != 0)
	    return PJ_RETURN_OS_ERROR(-rc);
    }
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_mutex_unlock(pj_mutex_t *mutex)
{
    PJ_ASSERT_RETURN(mutex, PJ_EINVAL);

    if (mutex->recursive) {
	pj_thread_t *this_thread = pj_thread_this();
	if (mutex->owner == this_thread) {
	    pj_assert(mutex->own_count > 0);
	    --mutex->own_count;
	    if (mutex->own_count == 0) {
		mutex->owner = NULL;
		up(&mutex->sem);
	    }
	} else {
	    pj_assert(!"Not owner!");
	    return PJ_EINVALIDOP;
	}
    } else {
	up(&mutex->sem);
    }
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_mutex_destroy(pj_mutex_t *mutex)
{
    PJ_ASSERT_RETURN(mutex != NULL, PJ_EINVAL);

    return PJ_SUCCESS;
}

#if defined(PJ_DEBUG) && PJ_DEBUG != 0
PJ_DEF(pj_bool_t) pj_mutex_is_locked(pj_mutex_t *mutex)
{
    if (mutex->recursive)
	return mutex->owner == pj_thread_this();
    else
	return 1;
}
#endif	/* PJ_DEBUG */


#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0

PJ_DEF(pj_status_t) pj_sem_create(  pj_pool_t *pool, 
                                    const char *name,
				    unsigned initial, 
                                    unsigned max,
				    pj_sem_t **sem)
{
    pj_sem_t *sem;

    PJ_UNUSED_ARG(max);

    PJ_ASSERT_RETURN(pool && sem, PJ_EINVAL);
    
    sem = pj_pool_alloc(pool, sizeof(pj_sem_t));
    sema_init(&sem->sem, initial);
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_sem_wait(pj_sem_t *sem)
{
    PJ_ASSERT_RETURN(pool && sem, PJ_EINVAL);

    down(&sem->sem);
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_sem_trywait(pj_sem_t *sem)
{
    int rc;

    PJ_ASSERT_RETURN(pool && sem, PJ_EINVAL);

    rc = down_trylock(&sem->sem);
    if (rc != 0) {
	return PJ_RETURN_OS_ERROR(-rc);
    } else {
	return PJ_SUCCESS;
    }
}

PJ_DEF(pj_status_t) pj_sem_post(pj_sem_t *sem)
{
    PJ_ASSERT_RETURN(pool && sem, PJ_EINVAL);

    up(&sem->sem);
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_sem_destroy(pj_sem_t *sem)
{
    PJ_ASSERT_RETURN(pool && sem, PJ_EINVAL);

    return PJ_SUCCESS;
}

#endif	/* PJ_HAS_SEMAPHORE */




