/*
 * Copyright (C) 2008 Philippe Gerum <rpm@xenomai.org>.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */

#ifndef _COPPERPLATE_THREADOBJ_H
#define _COPPERPLATE_THREADOBJ_H

#include <time.h>
#include <semaphore.h>
#include <signal.h>
#include <pthread.h>
#include <stdint.h>
#include <boilerplate/list.h>
#include <boilerplate/lock.h>
#include <boilerplate/sched.h>
#include <copperplate/clockobj.h>
#include <copperplate/heapobj.h>

#ifdef CONFIG_XENO_COBALT

#include <cobalt/uapi/kernel/types.h>

struct xnthread_user_window;

struct threadobj_corespec {
	xnhandle_t handle;
	union {
		__u32 u_winoff;
		struct xnthread_user_window *u_window;
	};
};

struct threadobj_stat {
	/** Execution time in primary mode (ns). */
	ticks_t xtime;
	/** Current timeout value (ns). */
	ticks_t timeout;
	/** Number of primary->secondary mode switches. */
	uint64_t msw;
	/** Number of context switches. */
	uint64_t csw;
	/** Number of Xenomai syscalls. */
	uint64_t xsc;
	/** Current CPU for thread. */
	int cpu;
	/** Scheduler lock nesting count. */
	int schedlock;
	/** Cobalt thread status bits. */
	unsigned int status;
	/** Number of page faults. */
	uint32_t pf;
};

#define SCHED_CORE  SCHED_COBALT

static inline
void threadobj_save_timeout(struct threadobj_corespec *corespec,
			    const struct timespec *timeout)
{
	/*
	 * We retrieve this information from the nucleus directly via
	 * cobalt_thread_stat().
	 */
}

#ifdef CONFIG_XENO_PSHARED

static inline struct xnthread_user_window *
threadobj_get_window(struct threadobj_corespec *corespec)
{
	extern void *cobalt_umm_shared;
	return (struct xnthread_user_window *)
		((caddr_t)cobalt_umm_shared + corespec->u_winoff);
}

#else /* !CONFIG_XENO_PSHARED */

static inline struct xnthread_user_window *
threadobj_get_window(struct threadobj_corespec *corespec)
{
	return corespec->u_window;
}

#endif /* !CONFIG_XENO_PSHARED */

#else  /* CONFIG_XENO_MERCURY */

#include <sys/time.h>

struct threadobj_corespec {
	pthread_cond_t grant_sync;
	int policy_unlocked;
	struct sched_param_ex schedparam_unlocked;
	timer_t rr_timer;
	/** Timeout reported by sysregd. */
	struct timespec timeout;
#ifdef CONFIG_XENO_WORKAROUND_CONDVAR_PI
	int policy_unboosted;
	struct sched_param_ex schedparam_unboosted;
#endif
};

struct threadobj_stat {
	/** Current timeout value (ns). */
	ticks_t timeout;
	/** Current CPU for thread. */
	int cpu;
	/** Scheduler lock nesting count. */
	int schedlock;
	/** Mercury thread status bits. */
	unsigned int status;
};

#define SCHED_CORE  SCHED_FIFO

static inline
void threadobj_save_timeout(struct threadobj_corespec *corespec,
			    const struct timespec *timeout)
{
	if (timeout)
		corespec->timeout = *timeout;
}

#endif /* CONFIG_XENO_MERCURY */

/*
 * threadobj->status, updated with ->lock held.
 */
#define __THREAD_S_STARTED	(1 << 0)	/* threadobj_start() called. */
#define __THREAD_S_WARMUP	(1 << 1)	/* threadobj_prologue() not called yet. */
#define __THREAD_S_ABORTED	(1 << 2)	/* Cancelled before start. */
#define __THREAD_S_LOCKED	(1 << 3)	/* threadobj_lock() granted (debug only). */
#define __THREAD_S_ACTIVE	(1 << 4)	/* Running user code. */
#define __THREAD_S_SUSPENDED	(1 << 5)	/* Suspended via threadobj_suspend(). */
#define __THREAD_S_SAFE		(1 << 6)	/* TCB release deferred. */
#define __THREAD_S_PERIODIC	(1 << 7)	/* Periodic timer set. */
#define __THREAD_S_DEBUG	(1 << 31)	/* Debug mode enabled. */
/*
 * threadobj->run_state, locklessly updated by "current", merged
 * with ->status bits by threadobj_get_status().
 */
#define __THREAD_S_RUNNING	0
#define __THREAD_S_DORMANT	(1 << 16)
#define __THREAD_S_WAIT		(1 << 17)
#define __THREAD_S_TIMEDWAIT	(1 << 18)
#define __THREAD_S_DELAYED	(1 << 19)
#define __THREAD_S_BREAK	(__THREAD_S_DELAYED|(1 << 20))

/* threadobj mode bits */
#define __THREAD_M_LOCK		(1 << 0) /* Toggle scheduler lock. */
#define __THREAD_M_WARNSW	(1 << 1) /* Toggle switch warning bit. */
#define __THREAD_M_CONFORMING	(1 << 2) /* Switch to conforming mode. */
#define __THREAD_M_SPARE0	(1 << 16)
#define __THREAD_M_SPARE1	(1 << 17)
#define __THREAD_M_SPARE2	(1 << 18)
#define __THREAD_M_SPARE3	(1 << 19)
#define __THREAD_M_SPARE4	(1 << 20)
#define __THREAD_M_SPARE5	(1 << 21)
#define __THREAD_M_SPARE6	(1 << 22)
#define __THREAD_M_SPARE7	(1 << 23)

/*
 * We need to use a valid address here. The object will never be dereferenced
 * when it is identified as IRQ context, so the pthread key itself is fine.
 */
#define THREADOBJ_IRQCONTEXT	((struct threadobj *)&threadobj_tskey)

struct traceobj;
struct syncobj;

struct threadobj {
	unsigned int magic;	/* Must be first. */
	pthread_t ptid;
	pthread_mutex_t lock;

	int schedlock_depth;
	int cancel_state;
	int status;
	int run_state;
	int policy;
	struct sched_param_ex schedparam;
	int global_priority;
	pid_t cnode;
	pid_t pid;
	char name[32];

	void (*finalizer)(struct threadobj *thobj);
	int core_offset;
	int *errno_pointer;
	/* Those members belong exclusively to the syncobj code. */
	struct syncobj *wait_sobj;
	struct holder wait_link;
	int wait_status;
	int wait_prio;
  	dref_type(void *) wait_union;
	size_t wait_size;
	timer_t periodic_timer;

	struct threadobj_corespec core;
	struct timespec tslice;
	pthread_cond_t barrier;
	struct traceobj *tracer;
	sem_t *cancel_sem;
	struct sysgroup_memspec memspec;
	struct backtrace_data btd;
};

struct threadobj_init_data {
	unsigned int magic;
	cpu_set_t affinity;
	int policy;
	struct sched_param_ex param_ex;
	void (*finalizer)(struct threadobj *thobj);
};

extern int threadobj_high_prio;

extern int threadobj_irq_prio;

extern pthread_key_t threadobj_tskey;

#ifdef HAVE_TLS

extern __thread __attribute__ ((tls_model (CONFIG_XENO_TLS_MODEL)))
struct threadobj *__threadobj_current;

static inline void threadobj_set_current(struct threadobj *thobj)
{
	__threadobj_current = thobj;
	pthread_setspecific(threadobj_tskey, thobj);
}

static inline struct threadobj *__threadobj_get_current(void)
{
	return __threadobj_current;
}

#else /* !HAVE_TLS */

static inline void threadobj_set_current(struct threadobj *thobj)
{
	pthread_setspecific(threadobj_tskey, thobj);
}

static inline struct threadobj *__threadobj_get_current(void)
{
	return (struct threadobj *)pthread_getspecific(threadobj_tskey);
}

#endif /* !HAVE_TLS */

static inline struct threadobj *threadobj_current(void)
{
	struct threadobj *thobj = __threadobj_get_current();
	return thobj == NULL || thobj == THREADOBJ_IRQCONTEXT ? NULL : thobj;
}

#ifdef CONFIG_XENO_DEBUG

static inline void __threadobj_tag_locked(struct threadobj *thobj)
{
	thobj->status |= __THREAD_S_LOCKED;
}

static inline void __threadobj_tag_unlocked(struct threadobj *thobj)
{
	assert(thobj->status & __THREAD_S_LOCKED);
	thobj->status &= ~__THREAD_S_LOCKED;
}

static inline void __threadobj_check_locked(struct threadobj *thobj)
{
	assert(thobj->status & __THREAD_S_LOCKED);
}

#else /* !CONFIG_XENO_DEBUG */

static inline void __threadobj_tag_locked(struct threadobj *thobj)
{
}

static inline void __threadobj_tag_unlocked(struct threadobj *thobj)
{
}

static inline void __threadobj_check_locked(struct threadobj *thobj)
{
}

#endif /* !CONFIG_XENO_DEBUG */

#ifdef __cplusplus
extern "C" {
#endif

void *__threadobj_alloc(size_t tcb_struct_size,
			size_t wait_union_size,
			int thobj_offset);

static inline void __threadobj_free(void *p)
{
	xnfree(p);
}

static inline void threadobj_free(struct threadobj *thobj)
{
	__threadobj_free((unsigned char *)thobj - thobj->core_offset);
}

int threadobj_init(struct threadobj *thobj,
		   struct threadobj_init_data *idata) __must_check;

int threadobj_start(struct threadobj *thobj) __must_check;

int threadobj_shadow(struct threadobj *thobj,
		     const char *name);

int threadobj_prologue(struct threadobj *thobj,
		       const char *name);

void threadobj_wait_start(void);

void threadobj_notify_entry(void);

int threadobj_cancel(struct threadobj *thobj);

void threadobj_uninit(struct threadobj *thobj);

int threadobj_suspend(struct threadobj *thobj);

int threadobj_resume(struct threadobj *thobj);

int threadobj_unblock(struct threadobj *thobj);

int __threadobj_lock_sched(struct threadobj *current);

int threadobj_lock_sched(void);

int __threadobj_unlock_sched(struct threadobj *current);

int threadobj_unlock_sched(void);

int threadobj_set_schedparam(struct threadobj *thobj, int policy,
			     const struct sched_param_ex *param_ex);

int threadobj_set_schedprio(struct threadobj *thobj, int priority);

int threadobj_set_mode(int clrmask, int setmask, int *mode_r);

int threadobj_set_periodic(struct threadobj *thobj,
			   const struct timespec *__restrict__ idate,
			   const struct timespec *__restrict__ period);

int threadobj_get_periodic(struct threadobj *thobj,
			   struct timespec *__restrict__ idate,
			   struct timespec *__restrict__ period);

int threadobj_wait_period(unsigned long *overruns_r) __must_check;

void threadobj_spin(ticks_t ns);

int threadobj_stat(struct threadobj *thobj,
		   struct threadobj_stat *stat);

int threadobj_sleep(const struct timespec *ts);

void threadobj_set_current_name(const char *name);

#ifdef CONFIG_XENO_PSHARED

static inline int threadobj_local_p(struct threadobj *thobj)
{
	extern pid_t __node_id;
	return thobj->cnode == __node_id;
}

#else /* !CONFIG_XENO_PSHARED */

static inline int threadobj_local_p(struct threadobj *thobj)
{
	return 1;
}

#endif	/* !CONFIG_XENO_PSHARED */

void threadobj_init_key(void);

int threadobj_pkg_init(int anon_session);

#ifdef __cplusplus
}
#endif

#define threadobj_alloc(T, __mptr, W)					\
	({								\
		void *__p;						\
		__p = __threadobj_alloc(sizeof(T), sizeof(W), offsetof(T, __mptr)); \
		__p;							\
	})

static inline int threadobj_get_policy(struct threadobj *thobj)
{
	return thobj->policy;
}

static inline int threadobj_get_priority(struct threadobj *thobj)
{
	return thobj->schedparam.sched_priority;
}

static inline void threadobj_copy_schedparam(struct sched_param_ex *param_ex,
					     const struct threadobj *thobj)
{
	*param_ex = thobj->schedparam;
}

static inline int threadobj_lock(struct threadobj *thobj)
{
	int ret;

	ret = write_lock_safe(&thobj->lock, thobj->cancel_state);
	if (ret)
		return ret;

	__threadobj_tag_locked(thobj);

	return 0;
}

static inline int threadobj_trylock(struct threadobj *thobj)
{
	int ret;

	ret = write_trylock_safe(&thobj->lock, thobj->cancel_state);
	if (ret)
		return ret;

	__threadobj_tag_locked(thobj);

	return 0;
}

static inline int threadobj_unlock(struct threadobj *thobj)
{
	__threadobj_check_locked(thobj);
	__threadobj_tag_unlocked(thobj);
	return write_unlock_safe(&thobj->lock, thobj->cancel_state);
}

static inline int threadobj_irq_p(void)
{
	struct threadobj *current = __threadobj_get_current();
	return current == THREADOBJ_IRQCONTEXT;
}

static inline int threadobj_current_p(void)
{
	return threadobj_current() != NULL;
}

static inline int __threadobj_lock_sched_once(struct threadobj *current)
{
	if (current->schedlock_depth == 0)
		return __threadobj_lock_sched(current);

	return -EBUSY;
}

static inline int threadobj_lock_sched_once(void)
{
	struct threadobj *current = threadobj_current();

	if (current->schedlock_depth == 0)
		return threadobj_lock_sched();

	return -EBUSY;
}

static inline void threadobj_yield(void)
{
	__RT(sched_yield());
}

static inline unsigned int threadobj_get_magic(struct threadobj *thobj)
{
	return thobj->magic;
}

static inline void threadobj_set_magic(struct threadobj *thobj,
				       unsigned int magic)
{
	thobj->magic = magic;
}

static inline int threadobj_get_lockdepth(struct threadobj *thobj)
{
	return thobj->schedlock_depth;
}

static inline int threadobj_get_status(struct threadobj *thobj)
{
	return thobj->status | thobj->run_state;
}

static inline int threadobj_get_errno(struct threadobj *thobj)
{
	return *thobj->errno_pointer;
}

#define threadobj_prepare_wait(T)					\
	({								\
		struct threadobj *__thobj = threadobj_current();	\
		assert(__thobj != NULL);				\
		assert(sizeof(typeof(T)) <= __thobj->wait_size);	\
		__mptr(__thobj->wait_union);				\
	})

#define threadobj_finish_wait()		do { } while (0)

static inline void *threadobj_get_wait(struct threadobj *thobj)
{
	return __mptr(thobj->wait_union);
}

static inline const char *threadobj_get_name(struct threadobj *thobj)
{
	return thobj->name;
}

static inline pid_t threadobj_get_pid(struct threadobj *thobj)
{
	return thobj->pid;
}

#ifdef CONFIG_XENO_WORKAROUND_CONDVAR_PI

int threadobj_cond_timedwait(pthread_cond_t *cond,
			     pthread_mutex_t *lock,
			     const struct timespec *timeout);

int threadobj_cond_wait(pthread_cond_t *cond,
			pthread_mutex_t *lock);

int threadobj_cond_signal(pthread_cond_t *cond);

int threadobj_cond_broadcast(pthread_cond_t *cond);

#else

static inline
int threadobj_cond_timedwait(pthread_cond_t *cond,
			     pthread_mutex_t *lock,
			     const struct timespec *timeout)
{
	return __RT(pthread_cond_timedwait(cond, lock, timeout));
}

static inline
int threadobj_cond_wait(pthread_cond_t *cond,
			pthread_mutex_t *lock)
{
	return __RT(pthread_cond_wait(cond, lock));
}

static inline
int threadobj_cond_signal(pthread_cond_t *cond)
{
	return __RT(pthread_cond_signal(cond));
}

static inline
int threadobj_cond_broadcast(pthread_cond_t *cond)
{
	return __RT(pthread_cond_broadcast(cond));
}

#endif /* !CONFIG_XENO_WORKAROUND_CONDVAR_PI */

#endif /* _COPPERPLATE_THREADOBJ_H */
