/*****************************************************************************/
/* 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.                                                                      */
/*                                                                           */
/*****************************************************************************/

#ifndef COMMON_H
#define COMMON_H

#include <tmosal.h>		/* tmosalStatistic_t */
#include <linux/version.h> /* LINUX_VERSION_CODE. */
#include <linux/module.h>	/* needed by all components */
#include <linux/spinlock.h>	/* spinlock_t */
#include <linux/workqueue.h>	/* work struct for handle table's garbage processing */
#include <linux/kernel.h>	/* printk() */
#include <linux/slab.h>		/* kmalloc() */
#include <linux/hardirq.h>	/* in_interrupt() */
#include <linux/sched.h>    /* TASK_UNINTERRUPTIBLE / TASK_INTERRUPTIBLE */


/* Simplest object allocator */

static inline char *allocate_zeroed(u32 size)
{
	char *res = kmalloc(size, GFP_KERNEL);
	memset(res, size, 0);
	return res;
}

#define ALLOCATE_ZEROED_MACRO(obj) do {				\
	obj = (typeof(obj)) allocate_zeroed(sizeof(*obj));	\
	if (obj)						\
		memset(obj, 0, sizeof(*obj));			\
} while (0)


/* Handles */

struct handle_node;
typedef void (*object_deallocator)(void *object);

struct handle_table {
	/* the current table and it's size */
	struct handle_node *table;
	u32 table_size;			/* not u16 to handle 0x10000 */

	/* list of nodes available for allocation.
	 * Both head and tail is stored to provide round-robin usage of nodes */
	s32 free_head, free_tail;	/* s32 to handle -1 */
	
	/* list of nodes queued for destruction; have to do in this
	 * complex way, because it's possible that dead node's usage counter
	 * will be zeroed in atomic context - clearly possible in SMP case,
	 * not sure about UP, but better to be safer :) */
	s32 garbage_head;
	object_deallocator deallocator;	/* kfree() is used if unset */
	struct work_struct work;	/* need a work_struct per handle_table */
	
	/* any usage is under lock; table pointer may get changed while
	 * lock is not held */
	spinlock_t lock;

	/* name is used just to log error messages about not deallocated
	 * objects at deinitialization time */
	const char *name;
};


/* Initial table allocation and structure initialization */
int init_handle_table(struct handle_table *ht, int initial_size, const char *name);

/* Table deallocation.
 * Objects are not destructed. Just a warning is printk()ed for each left
 * object */
void uninit_handle_table(struct handle_table *ht);

/* Handle allocation. Returns 0 on success, -ENOMEM on no memory
 * condition (which may happen on table reallocation only). */
int allocate_handle(struct handle_table *ht, u32 *result);

/* Set the handle's object. Usage count is set to zero here */
void bind_object_to_handle(struct handle_table *ht, u32 handle, void *object);

/* Cancel handle allocation. This should be called if handle was allocated,
 * but object could not be created */
void cancel_handle(struct handle_table *ht, u32 handle);

/* Get the object by handle.
 * On success, will increase useage counter, and return object.
 * May also return zero (for never-existed object), or UNSET_UBJECT,
 * or DEAD_OBJECT */
void *get_object(struct handle_table *ht, u32 handle);

/* Decrease object usage counter; destruct object if usage counter is zero */
void put_object(struct handle_table *ht, u32 handle);

/* Mark object as dead. Nothing else is done - no usage count changes, etc */
void mark_dead_object(struct handle_table *ht, u32 handle);

/* Utility to search in existing objects. Returned object gets usage count
 * increased */
typedef int (*object_condition)(void *object, void *param);
void *search_object(struct handle_table *ht, object_condition cond, void *param);

/* Utility to check if handle still corresponds to a non-dead object */
int object_alive(struct handle_table *ht, u32 handle);

#define ALLOCATE_HANDLE_MACRO(ht, handle, err_return) do {		\
	if (unlikely(allocate_handle(ht, (u32 *)(&handle)) != 0))	\
		return err_return;					\
} while (0)

#define BIND_OBJECT_MACRO(ht, handle, obj) \
	bind_object_to_handle(ht, handle, obj)

#define CANCEL_HANDLE_MACRO(ht, handle) cancel_handle(ht, handle)
	
#define GET_OBJECT_MACRO(ht, handle, obj, err_return) do {		\
	obj = (typeof(obj)) get_object(ht, handle);			\
	if (unlikely(!obj))						\
		return err_return;					\
} while (0)

#define PUT_OBJECT_MACRO(ht, handle) put_object(ht, handle)

#define MARK_DEAD_OBJECT_MACRO(ht, handle) mark_dead_object(ht, handle)


#define TASK_HANDLE_TABLE_INITIAL_SIZE 32
extern int osal_task_submodule_init(void);
extern void osal_task_submodule_uninit(void);

#define TIMER_HANDLE_TABLE_INITIAL_SIZE 32
extern int osal_time_submodule_init(void);
extern void osal_time_submodule_uninit(void);

/* int handle table size is set automatically */
extern int osal_int_submodule_init(void);
extern void osal_int_submodule_uninit(void);

#define SEM_HANDLE_TABLE_INITIAL_SIZE 32
extern int osal_sem_submodule_init(void);
extern void osal_sem_submodule_uninit(void);

#define MUTEX_HANDLE_TABLE_INITIAL_SIZE 32
extern int osal_mutex_submodule_init(void);
extern void osal_mutex_submodule_uninit(void);

#define EVENT_HANDLE_TABLE_INITIAL_SIZE 32
extern int osal_event_submodule_init(void);
extern void osal_event_submodule_uninit(void);

#define QUEUE_HANDLE_TABLE_INITIAL_SIZE 32
extern int osal_queue_submodule_init(void);
extern void osal_queue_submodule_uninit(void);

#ifdef CONFIG_FW_LOADER
extern int osal_firmware_submodule_init(void);
extern void osal_firmware_submodule_uninit(void);
#endif
	
/* Macros for avoiding in-interrupt usage of functions */

#define BUG_IF_INTERRUPT() do {			\
	if (in_interrupt()) {			\
		printk(KERN_ERR "%s() called from interrupt context\n", __FUNCTION__); \
		BUG();				\
	}					\
} while (0)

#ifdef CONFIG_PREEMPT
#define BUG_IF_NOT_PREEMPTIBLE() do {		\
	if (in_atomic()) {			\
		printk(KERN_ERR "%s() called from atomic context\n", __FUNCTION__); \
		BUG();				\
	}					\
} while (0)
#else
#define BUG_IF_NOT_PREEMPTIBLE() do {} while (0)
#endif

#define BUG_IF_SLEEP_IN_ATOMIC_C1(pt) do {	\
	if ((!pt || *pt > 0) && in_interrupt()) { \
		printk(KERN_ERR "%s() called with positive timeout from interrupt context\n", __FUNCTION__); \
		BUG();				\
	}					\
} while (0)

#define BUG_IF_SLEEP_IN_ATOMIC_C2(pt) do {	\
	if ((!pt || *pt > 0) && in_atomic()) {	\
		printk(KERN_ERR "%s() called with positive timeout from atomic context\n", __FUNCTION__); \
		BUG();				\
	}					\
} while (0)

#ifdef CONFIG_PREEMPT
#define BUG_IF_SLEEP_IN_ATOMIC(pt) do {		\
	BUG_IF_SLEEP_IN_ATOMIC_C1(pt);		\
	BUG_IF_SLEEP_IN_ATOMIC_C2(pt);		\
} while (0)
#else
#define BUG_IF_SLEEP_IN_ATOMIC(pt) BUG_IF_SLEEP_IN_ATOMIC_C1(pt)
#endif

/* Module parameters */
extern int rr_prio_start, rr_prio_mapping, prio_1_mapping, osal_debug_level;

/* Statistics helper */

#ifdef PERF_TESTING

extern tmosalStatistic_t osal_stat;
extern spinlock_t osal_stat_lock;

#define STAT_HANDLE_CREATE(number, max) do {	\
	spin_lock(&osal_stat_lock);		\
	osal_stat.number++;			\
	if (osal_stat.max < osal_stat.number)	\
		osal_stat.max = osal_stat.number; \
	spin_unlock(&osal_stat_lock);		\
} while (0)

#define STAT_HANDLE_DESTROY(number) do {	\
	spin_lock(&osal_stat_lock);		\
	osal_stat.number--;			\
	spin_unlock(&osal_stat_lock);		\
} while (0)

#else

#define STAT_HANDLE_CREATE(number, max) do {} while (0)
#define STAT_HANDLE_DESTROY(number) do {} while (0)

#endif	/* PERF_TESTING */
#endif
