/*****************************************************************************
* wanpipe_logger.c
*
* 		WANPIPE(tm) - Event Logger API Support
*
* Authors: 	David Rokhvarg <davidr@sangoma.com>
*
* Copyright:	(c) 2003-2009 Sangoma Technologies Inc.
*
* ============================================================================
* September 04, 2009	David Rokhvarg	Initial version.
*****************************************************************************/

#include "wanpipe_includes.h"
#include "wanpipe_cdev_iface.h"
#include "wanpipe_logger.h"

/*=================================================================
 * Debugging Definitions
 *================================================================*/

#if defined(__WINDOWS__)
# define DEBUG_LOGGER if(0)DbgPrint
extern void vOutputLogString(const char * pvFormat, va_list args);
#else
# define DEBUG_LOGGER if(0)printk
#endif

#undef LOGGER_FUNC_DEBUG
#if 0
# define LOGGER_FUNC_DEBUG()  DEBUG_LOGGER("%s():Line:%d\n", __FUNCTION__, __LINE__)
#else
# define LOGGER_FUNC_DEBUG()
#endif

#define LOGGER_QLEN_DBG(queue)	\
if(0){	\
	DEBUG_LOGGER("%s(): queue ptr: 0x%p (%s), qlen:%d\n",	\
		__FUNCTION__,	\
		queue, \
		(queue == &logger_api_dev.wp_event_list ? "wp_event_list":"wp_event_free_list"),	\
		wan_skb_queue_len(queue));	\
}

#define WAN_MESSAGE_DISCARD_COUNT 1000

/*=================================================================
 * Macro Definitions
 *================================================================*/


#define WAN_LOGGER_SET_DATA(logger_event, logger_type, evt_type, format, va_arg_list)	\
{	\
	\
	memset(logger_event, 0x00, sizeof(*logger_event));	\
	\
	logger_event->logger_type = logger_type;	\
	logger_event->event_type = evt_type;	\
	\
	wp_vsnprintf(logger_event->data, sizeof(logger_event->data), format, va_arg_list);	\
	\
	WAN_LOGGER_SET_TIME_STAMP(logger_event)	\
}

#if defined (KERN_DO_GET_TIME) && (KERN_DO_GET_TIME == 0)
#define WAN_LOGGER_SET_TIME_STAMP(logger_event) \
{       \
	struct timespec64 tv;           \
	\
	ktime_get_real_ts64(&tv);;      \
	\
	logger_event->time_stamp_sec = tv.tv_sec;               \
	logger_event->time_stamp_usec = tv.tv_nsec/1000;        \
}
#else
#define WAN_LOGGER_SET_TIME_STAMP(logger_event)	\
{	\
	struct timeval tv;	\
	\
	do_gettimeofday(&tv);	\
	\
	logger_event->time_stamp_sec = tv.tv_sec;	\
	logger_event->time_stamp_usec = tv.tv_usec;	\
}
#endif

/*=================================================================
 * Static Defines
 *================================================================*/

typedef struct wp_logger_api_dev {

	u32 	init;
	char 	name[WAN_IFNAME_SZ];

	wan_spinlock_t lock;

	u32	used, open_cnt;

	wanpipe_api_dev_cfg_t	cfg;

	wan_skb_queue_t 	wp_event_list;
	wan_skb_queue_t 	wp_event_free_list;

	void 			*cdev;
	u32				magic_no;

} wp_logger_api_dev_t;


/*=================================================================
 * Function Prototypes and Global Variables
 *================================================================*/

u_int32_t wp_logger_level_default = SANG_LOGGER_INFORMATION | SANG_LOGGER_WARNING | SANG_LOGGER_ERROR;
u_int32_t wp_logger_level_te1 = 0;
u_int32_t wp_logger_level_hwec = 0;
u_int32_t wp_logger_level_tdmapi = 0;
u_int32_t wp_logger_level_fe = 0;
u_int32_t wp_logger_level_bri = 0;

/* by default messages will be printed into Log file. */
static u_int32_t wp_logger_level_file = SANG_LOGGER_FILE_ON;


static wp_logger_api_dev_t	logger_api_dev;
static wanpipe_cdev_ops_t	wp_logger_api_fops;

static void __wp_logger_input(u_int32_t logger_type, u_int32_t evt_type, const char * fmt, ...);

/*=================================================================
 * Private Functions
 *================================================================*/

static u_int32_t* wp_logger_type_to_variable_ptr(u_int32_t logger_type)
{
	switch(logger_type)
	{
	case WAN_LOGGER_DEFAULT:
		return &wp_logger_level_default;
	case WAN_LOGGER_TE1:
		return &wp_logger_level_te1;
	case WAN_LOGGER_HWEC:
		return &wp_logger_level_hwec;
	case WAN_LOGGER_TDMAPI:
		return &wp_logger_level_tdmapi;
	case WAN_LOGGER_FE:
		return &wp_logger_level_fe;
	case WAN_LOGGER_BRI:
		return &wp_logger_level_bri;
	case WAN_LOGGER_FILE:
		return &wp_logger_level_file;
	default:
		return NULL;
	}
}

static void wp_logger_spin_lock_irq(wan_smp_flag_t *flags)
{
	wan_spin_lock_irq(&logger_api_dev.lock, flags);
}

static void wp_logger_spin_unlock_irq(wan_smp_flag_t *flags)
{
	wan_spin_unlock_irq(&logger_api_dev.lock, flags);
}

static int wp_logger_increment_open_cnt(void)
{
	logger_api_dev.open_cnt++;
	return logger_api_dev.open_cnt;
}

static int wp_logger_decrement_open_cnt(void)
{
	logger_api_dev.open_cnt--;
	return logger_api_dev.open_cnt;
}

static int wp_logger_get_open_cnt(void)
{
	return logger_api_dev.open_cnt;
}

/* set logger level bitmap */
static void wp_logger_set_level(u_int32_t logger_type, u_int32_t new_level)
{
	u_int32_t *logger_var_ptr = wp_logger_type_to_variable_ptr(logger_type);

	DEBUG_LOGGER("%s(): logger_type: %d(%s), new_level: 0x%08X\n",
		__FUNCTION__, logger_type, SANG_DECODE_LOGGER_TYPE(logger_type),
		new_level);

	if(logger_var_ptr){
		*logger_var_ptr = new_level;
	}else{
		DEBUG_WARNING("Warning: %s(): unknown logger_type: %d\n",
			__FUNCTION__, logger_type);
	}
}


/* return current logger level bitmap to the caller */
static u_int32_t wp_logger_get_level(u_int32_t logger_type)
{
	u_int32_t *logger_var_ptr = wp_logger_type_to_variable_ptr(logger_type);

	if(logger_var_ptr){
		DEBUG_LOGGER("%s(): logger_type: %d, current level: 0x%08X\n", 
			__FUNCTION__, logger_type, *logger_var_ptr);

		return *logger_var_ptr;
	}else{
		DEBUG_WARNING("Warning: %s(): unknown logger_type: %d\n",
			__FUNCTION__, logger_type);
		return 0;
	}
}

static void wp_logger_init_buffs(void)
{
	netskb_t *skb;
	wan_smp_flag_t irq_flag;

	wp_logger_spin_lock_irq(&irq_flag);

	while((skb = wan_skb_dequeue(&logger_api_dev.wp_event_list))) {
		wan_skb_init(skb,0);
		wan_skb_trim(skb,0);
		wan_skb_queue_tail(&logger_api_dev.wp_event_free_list, skb);
	}

	wp_logger_spin_unlock_irq(&irq_flag);
}

static int wp_logger_api_open(void *not_used)
{
	if (wp_logger_increment_open_cnt() == 1) {
		wp_logger_init_buffs();
	}

	wan_set_bit(0,&logger_api_dev.used);

	DEBUG_LOGGER ("%s(): OpenCounter: %d\n",
		__FUNCTION__, wp_logger_get_open_cnt());
	return 0;
}

static void wp_logger_wakeup_api(void)
{
	if (logger_api_dev.cdev) {
		wanpipe_cdev_event_wake(logger_api_dev.cdev);
	}
}

static int wp_logger_api_release(void *not_used)
{
	u32 cnt;

	if (!wan_test_bit(0, &logger_api_dev.init)) {
		wan_clear_bit(0, &logger_api_dev.used);
		return -ENODEV;
	}

	cnt = wp_logger_decrement_open_cnt();

	if (cnt == 0) {

		wan_clear_bit(0, &logger_api_dev.used);

		wp_logger_wakeup_api();
	}

	return 0;
}

static int wp_logger_alloc_q(wan_skb_queue_t *queue)
{
	netskb_t *skb;
	int i;
	wan_smp_flag_t irq_flag;

	for (i = 0; i < WP_MAX_NO_LOGGER_EVENTS; i++) {

		skb = wan_skb_kalloc(sizeof(wp_logger_event_t));

		if (!skb) {
			if (WAN_NET_RATELIMIT()) {
				DEBUG_ERROR("Error: %s(): %d: Failed to Allocate Memory!\n",
					__FUNCTION__, __LINE__);
			}
			return -1;
		}

		/*wan_skb_init(skb, sizeof(wp_logger_event_t));*/
		wan_skb_init(skb, 0);
		wan_skb_trim(skb, 0);

		wp_logger_spin_lock_irq(&irq_flag);	
		wan_skb_queue_tail(queue, skb);
		wp_logger_spin_unlock_irq(&irq_flag);
	}

	LOGGER_QLEN_DBG(queue);
	return 0;
}

static void wp_logger_free_q(wan_skb_queue_t *queue)
{
	netskb_t *skb;
	wan_smp_flag_t irq_flag;

	LOGGER_QLEN_DBG(queue);

	do {
		wp_logger_spin_lock_irq(&irq_flag);

		skb = wan_skb_dequeue(queue);

		wp_logger_spin_unlock_irq(&irq_flag);

		if(skb){
			wan_skb_free(skb);
		}
	}while(skb);

}

/* Note that there is no need to check for queue length exceeding
 * some maximum because it is done automatically, when wp_logger_get_free_skb()
 * returns NULL. */
static void wp_logger_enqueue_skb(wan_skb_queue_t *queue, netskb_t *skb)
{
	wan_smp_flag_t irq_flag;

	wp_logger_spin_lock_irq(&irq_flag);
	wan_skb_queue_tail(queue, skb);
	wp_logger_spin_unlock_irq(&irq_flag);
}

static netskb_t* wp_logger_dequeue_skb(wan_skb_queue_t *queue)
{
	netskb_t *skb;
	wan_smp_flag_t irq_flag;

	wp_logger_spin_lock_irq(&irq_flag);
	skb = wan_skb_dequeue(queue);
	wp_logger_spin_unlock_irq(&irq_flag);

	return skb;
}

static unsigned int wp_logger_api_poll(void *not_used)
{
	int ret = 0, qlength;
	wan_smp_flag_t irq_flag;	


	if (!wan_test_bit(0, &logger_api_dev.init) || !wan_test_bit(0, &logger_api_dev.used)){
		return -ENODEV;
	}

	wp_logger_spin_lock_irq(&irq_flag);

	qlength = wan_skb_queue_len(&logger_api_dev.wp_event_list);

	wp_logger_spin_unlock_irq(&irq_flag);

	if (qlength) {
		/* Indicate an exception */
		ret = POLLPRI;
	}

	return ret;	
}

static int wp_logger_handle_api_cmd(void *user_data)
{
	wp_logger_cmd_t tmp_usr_logger_api, *user_logger_api_ptr;
	int err;
	netskb_t *skb;
	wan_smp_flag_t irq_flag;	

	user_logger_api_ptr = (wp_logger_cmd_t*)user_data;

#if defined(__WINDOWS__)
	/* user_data IS a kernel-space pointer to wp_logger_cmd_t */
	memcpy(&tmp_usr_logger_api, user_data, sizeof(wp_logger_cmd_t));
#else
	if(WAN_COPY_FROM_USER(&tmp_usr_logger_api, user_logger_api_ptr, sizeof(wp_logger_cmd_t))){
		return -EFAULT;
	}
#endif

	err = tmp_usr_logger_api.result = SANG_STATUS_SUCCESS;

	DEBUG_LOGGER("%s: Logger CMD: %i, sizeof(wp_logger_cmd_t)=%d\n",
			logger_api_dev.name, tmp_usr_logger_api.cmd, (unsigned int)sizeof(wp_logger_cmd_t));

	switch (tmp_usr_logger_api.cmd) 
	{
	case WP_API_LOGGER_CMD_READ_EVENT:

		skb = wp_logger_dequeue_skb(&logger_api_dev.wp_event_list);
		if (!skb){
			err = SANG_STATUS_NO_FREE_BUFFERS;
			break;
		}
		
		memcpy(&tmp_usr_logger_api.logger_event, wan_skb_data(skb), sizeof(wp_logger_event_t));
	
		/* return the buffer into free skb list (do NOT deallocate it!) */
		wp_logger_enqueue_skb(&logger_api_dev.wp_event_free_list, skb);
		break;
		
	case WP_API_LOGGER_CMD_GET_LOGGER_LEVEL:
		tmp_usr_logger_api.logger_level_ctrl.logger_level =
			wp_logger_get_level(tmp_usr_logger_api.logger_level_ctrl.logger_type);
		break;
		
	case WP_API_LOGGER_CMD_SET_LOGGER_LEVEL:
		wp_logger_set_level(tmp_usr_logger_api.logger_level_ctrl.logger_type, 
						tmp_usr_logger_api.logger_level_ctrl.logger_level);
		break;
		
	case WP_API_LOGGER_CMD_OPEN_CNT:
		tmp_usr_logger_api.open_cnt = wp_logger_get_open_cnt();
		break;

	case WP_API_LOGGER_CMD_GET_STATS:

		wp_logger_spin_lock_irq(&irq_flag);

		tmp_usr_logger_api.stats.max_event_queue_length = wan_skb_queue_len(&logger_api_dev.wp_event_list) + wan_skb_queue_len(&logger_api_dev.wp_event_free_list);
		tmp_usr_logger_api.stats.current_number_of_events_in_event_queue = wan_skb_queue_len(&logger_api_dev.wp_event_list);

		wp_logger_spin_unlock_irq(&irq_flag);

		tmp_usr_logger_api.stats.rx_events_dropped	= logger_api_dev.cfg.stats.rx_events_dropped;
		tmp_usr_logger_api.stats.rx_events 		= logger_api_dev.cfg.stats.rx_events;
		break;

	case WP_API_LOGGER_CMD_RESET_STATS:
		memset(&logger_api_dev.cfg.stats, 0x00, sizeof(logger_api_dev.cfg.stats));
		break;

	case WP_API_LOGGER_CMD_FLUSH_BUFFERS:
		wp_logger_init_buffs();
		break;

	default:
		DEBUG_WARNING("%s: Warning: Invalid Wanpipe Logger API Command: %i\n",
			logger_api_dev.name, tmp_usr_logger_api.cmd);
		err = SANG_STATUS_OPTION_NOT_SUPPORTED;
		break;

	}/* switch (tmp_usr_logger_api.cmd)  */

	tmp_usr_logger_api.result = err;

#if defined(__WINDOWS__)
	/* user_data IS a kernel-space pointer to wp_logger_cmd_t */
	memcpy(user_data, &tmp_usr_logger_api, sizeof(wp_logger_cmd_t));
#else
	if(WAN_COPY_TO_USER(user_data, &tmp_usr_logger_api, sizeof(wp_logger_cmd_t))){
	    err = -EFAULT;
	}
#endif

	return err;
}

static int wp_logger_api_ioctl(void *not_used, int ioctl_cmd, void *user_data)
{
	int err = 0;

	if (!wan_test_bit(0,&logger_api_dev.init) || WANPIPE_MAGIC != logger_api_dev.magic_no){
		DEBUG_ERROR("Error: %s(): line: %d: Logger API Not initialized!\n",
			__FUNCTION__, __LINE__);
		return -EFAULT;
	}

	if (!user_data){
		return -EINVAL;
	}

	switch (ioctl_cmd) 
	{
	case WANPIPE_IOCTL_LOGGER_CMD:
		err = wp_logger_handle_api_cmd(user_data);
		break;

	default:
		DEBUG_ERROR("Error: %s(): line: %d: Invalid IOCTL CMD: %d!\n",
			__FUNCTION__, __LINE__, ioctl_cmd);
		err = -EINVAL;
		break;
	}	

	return err;
}

static netskb_t* wp_logger_get_free_skb(void)
{
	netskb_t *skb;

	skb = wp_logger_dequeue_skb(&logger_api_dev.wp_event_free_list);

	if (skb == NULL) {
		DEBUG_ERROR("Error: %s(): no free buffers. Dropping Logger Event!\n",
			__FUNCTION__);
		WP_AFT_CHAN_ERROR_STATS(logger_api_dev.cfg.stats, rx_events_dropped);
		return NULL;
	}

	return skb;
}

static int wp_logger_push_event(netskb_t *skb)
{
	if (!wan_test_bit(0,&logger_api_dev.init) || WANPIPE_MAGIC != logger_api_dev.magic_no){
		DEBUG_TEST("%s(): Initialization incomplete. Dropping event.\n", __FUNCTION__);
		return -ENODEV;
	}

	if (!skb) {
		DEBUG_ERROR("Error: Logger: Null skb argument in %s()!\n", __FUNCTION__);
		return -ENODEV;
	}

	if (!wan_test_bit(0,&logger_api_dev.used)) {
		DEBUG_TEST("%s(): Not in use. Dropping event.\n", __FUNCTION__);
		return -EBUSY;
	}

	wp_logger_enqueue_skb(&logger_api_dev.wp_event_list, skb);

	logger_api_dev.cfg.stats.rx_events++;

	return 0;
}

static char previous_error_message[WP_MAX_NO_BYTES_IN_LOGGER_EVENT];
static u32	repeating_error_message_counter = 0;
static wan_ticks_t wp_logger_max_msg_timeout=0;
static u32  wp_logger_max_msg_cnt=0;

#define WP_MAX_ERROR_MSG_PER_SEC 100

static int wp_logger_global_max_limit_check (u_int32_t logger_type, u_int32_t evt_type, char *tmp_message_buf)
{

	wp_logger_max_msg_cnt++;

	if ((SYSTEM_TICKS - wp_logger_max_msg_timeout) > HZ) {
		wp_logger_max_msg_timeout=SYSTEM_TICKS;
		wp_logger_max_msg_cnt=0;
		return 0;
	}

	if (wp_logger_max_msg_cnt == WP_MAX_ERROR_MSG_PER_SEC) {
		wp_snprintf(tmp_message_buf, WP_MAX_NO_BYTES_IN_LOGGER_EVENT-1,
				"wanpipe: Global message per sec %d exceeded.\n", WP_MAX_ERROR_MSG_PER_SEC);
		__wp_logger_input(logger_type, evt_type, "%s", tmp_message_buf);
		return 1;
	}

	if (wp_logger_max_msg_cnt > WP_MAX_ERROR_MSG_PER_SEC) {
		return 1;
	}

	return 0;
}



static int wp_logger_repeating_message_filter(u_int32_t logger_type, u_int32_t evt_type, const char * fmt, va_list *va_arg_list)
{
	char current_error_message[WP_MAX_NO_BYTES_IN_LOGGER_EVENT];
	char tmp_message_buf[WP_MAX_NO_BYTES_IN_LOGGER_EVENT];

	/* Filter out repeating messages. */
	u8	apply_filter = 0;

	/* Filter only ERROR message from Default logger. 
	 * Other types of message can be filtered too. */
	if(WAN_LOGGER_DEFAULT == logger_type){
 		if(SANG_LOGGER_ERROR == evt_type){
			apply_filter = 1;
		}
	}

	if(!apply_filter){
		/* consider it as NOT a repeating message */
		previous_error_message[0]=0;
		repeating_error_message_counter = 0;
		return 0;
	}

	/* Only allow limited number of filtered messags per sec for the whole diver.
	   This prevents edge scenarios where driver can start printing thousands of
	   messages per sec */
	if (wp_logger_global_max_limit_check(logger_type, evt_type, tmp_message_buf)) {
		return 1;
	}

	memset(current_error_message, 0x00, sizeof(current_error_message));

	wp_vsnprintf(current_error_message, sizeof(current_error_message) - 1,
		(const char *)fmt, *va_arg_list);

	if(!memcmp(previous_error_message, current_error_message, sizeof(current_error_message))){
		/* Every WAN_MESSAGE_DISCARD_COUNT messages print the repeating message
		 * and the counter how many times it was repeated. */

		if(!(repeating_error_message_counter % WAN_MESSAGE_DISCARD_COUNT)){
		
			if (repeating_error_message_counter) {

				/* print the repeating message */
				__wp_logger_input(logger_type, evt_type, "%s", current_error_message);

				/* and say how many times it was repeated. */
				wp_snprintf(tmp_message_buf, sizeof(tmp_message_buf),
					"wanpipe: Message repeated %d times.\n", repeating_error_message_counter);
				__wp_logger_input(logger_type, evt_type, "%s", tmp_message_buf);
			}
		}

		++repeating_error_message_counter;

		/* is IS a repeating message */
		return 1;

	}else{

		if(repeating_error_message_counter){

			/* this message broke a sequence of repeating messages */
			wp_snprintf(tmp_message_buf, sizeof(tmp_message_buf),
				"wanpipe: Message repeated %d times.\n", repeating_error_message_counter);
			__wp_logger_input(logger_type, evt_type, "%s", tmp_message_buf);
			repeating_error_message_counter = 0;
		}

		/* store current message for comparison with a future message */
		memcpy(previous_error_message, current_error_message, sizeof(current_error_message));

		/* it is NOT a repeating message */
		return 0;
	}
}

/* no Filtering will be applied to the message */
static void wp_logger_vInput(u_int32_t logger_type, u_int32_t evt_type, const char * fmt, va_list *va_arg_list)
{
	/************************************************************************
	  Independently of Logger Device state, write the message into log file. 
	*************************************************************************/

	if (!(wp_logger_level_file & SANG_LOGGER_FILE_OFF)) {

 #ifdef __WINDOWS__
		/* Write the message to wanpipelog.txt */
		vOutputLogString(fmt, *va_arg_list);
 #else
 		{
			char msg[WP_MAX_NO_BYTES_IN_LOGGER_EVENT];
			wp_vsnprintf(msg, sizeof(msg) - 1,(const char *)fmt, *va_arg_list);
			switch (evt_type) {
			case SANG_LOGGER_ERROR:
				printk(KERN_ERR "%s", msg);
				break;
			case SANG_LOGGER_WARNING:
				printk(KERN_WARNING "%s", msg);
				break;
			case SANG_LOGGER_INFORMATION:
			default:
#ifdef WAN_DEBUG_EVENT_AS_KERN_DEBUG
				printk(KERN_DEBUG "%s", msg);
#else
				printk(KERN_INFO "%s", msg);
#endif
				break;
			}
		}
 #endif
	}

	/************************************************************************
	 Push the message into logger queue.
	*************************************************************************/

	if (WANPIPE_MAGIC == logger_api_dev.magic_no	&&
		wan_test_bit(0, &logger_api_dev.used)		&&
		wan_test_bit(0, &logger_api_dev.init)){

		wp_logger_event_t *p_wp_logger_event;
		netskb_t *skb;

		skb = wp_logger_get_free_skb();
		if(skb){
			wan_skb_init(skb,0);
			wan_skb_trim(skb,0);
			
			/* since we have the free skb buffer we should copy data into it
			* and push it into event queue. */
			p_wp_logger_event = (wp_logger_event_t*)wan_skb_put(skb, sizeof(wp_logger_event_t));
			
			memset(p_wp_logger_event, 0, sizeof(wp_logger_event_t));
			
			WAN_LOGGER_SET_DATA(p_wp_logger_event, logger_type, evt_type, fmt, *va_arg_list);
			
			wp_logger_push_event(skb);

			wp_logger_wakeup_api();

#ifdef AFT_TASKQ_DEBUG
			DEBUG_TASKQ("%s():%d trigger executed!\n",
					__FUNCTION__,__LINE__);
#endif
		}
	}
}

static void __wp_logger_input(u_int32_t logger_type, u_int32_t evt_type, const char * fmt, ...)
{
    va_list	va_arg_list;

	/* the paramter list must start at fmt, not at evt_type */
    va_start (va_arg_list, fmt);

	wp_logger_vInput(logger_type, evt_type, fmt, &va_arg_list);

    va_end (va_arg_list);
}

/*=================================================================
 * Public Functions
 *================================================================*/

/* Windows note: sngbus.sys, sprotocol.sys and wanpipe.sys 
 * will NOT initialize/use Logger Device, but writing to wanpipelog.txt
 * file is still possible by calling this function.*/
void wp_logger_input(u_int32_t logger_type, u_int32_t evt_type, const char * fmt, ...)
{
	va_list	va_arg_list;

	/* Linux 64bit: Note that va_arg_list is started/ended twice -
	 * before each call to functions which take it as a parameter.
	 */

	/* the paramter list must start at fmt, not at evt_type */
	va_start (va_arg_list, fmt);
	if (wp_logger_repeating_message_filter(logger_type, evt_type, fmt, &va_arg_list)) {
		va_end (va_arg_list);
		return;
	}
	va_end (va_arg_list);

    va_start (va_arg_list, fmt);
    wp_logger_vInput(logger_type, evt_type, fmt, &va_arg_list);
	va_end (va_arg_list);
}

/* This function runs during module load - the 'logger_api_dev' structure
 * is initialized for the first time. */
int wp_logger_create(void)
{
	wanpipe_cdev_t *cdev;
	int err;
		
	if(WANPIPE_MAGIC == logger_api_dev.magic_no){
		DEBUG_LOGGER("%s(): Logger Dev already exist - not creating\n", __FUNCTION__);
		/* Already initialized. */
		return 0;
	}

	memset(&logger_api_dev, 0, sizeof(wp_logger_api_dev_t));
	logger_api_dev.magic_no = WANPIPE_MAGIC;

	snprintf(logger_api_dev.name, sizeof(logger_api_dev.name), "WP Logger");

	wp_logger_api_fops.open		= wp_logger_api_open;
	wp_logger_api_fops.close	= wp_logger_api_release;
	wp_logger_api_fops.ioctl	= wp_logger_api_ioctl;
	wp_logger_api_fops.read		= NULL;
	wp_logger_api_fops.write	= NULL;
	wp_logger_api_fops.poll		= wp_logger_api_poll;
	
	cdev = wan_kmalloc(sizeof(wanpipe_cdev_t));
	if (!cdev) {
		return -ENOMEM;
	}
	memset(cdev, 0, sizeof(wanpipe_cdev_t));
	
	DEBUG_LOGGER("%s(): Registering Wanpipe Logger Device!\n",__FUNCTION__);
	
	wan_skb_queue_init(&logger_api_dev.wp_event_list);
	wan_skb_queue_init(&logger_api_dev.wp_event_free_list);
	
	wan_spin_lock_init(&logger_api_dev.lock, "wp_logger_lock");

	if(wp_logger_alloc_q(&logger_api_dev.wp_event_free_list)){
		logger_api_dev.magic_no = 0;
		return -ENOMEM;
	}
		
	cdev->dev_ptr = &logger_api_dev;

	logger_api_dev.cdev = cdev;
	
	memcpy(&cdev->ops, &wp_logger_api_fops, sizeof(cdev->ops));
	
	err = wanpipe_cdev_logger_create(cdev);

	if(err){
		logger_api_dev.magic_no = 0;
		return err;
	}

	wan_set_bit(0,&logger_api_dev.init);

	return 0;
}

/* This function called during module unload. */
void wp_logger_delete(void)
{
	if(wan_test_bit(0,&logger_api_dev.used)){
		DEBUG_LOGGER("%s(): Logger Dev in use - not deleting\n", __FUNCTION__);
		/* If open file descriptor exist, deletion of Logger Device
		 * will fail (under Windows). */
		return;
	}

	if(WANPIPE_MAGIC != logger_api_dev.magic_no){
		/* make sure logger deleted exactly one time */
		return;
	}

	logger_api_dev.magic_no = 0;
	wan_clear_bit(0, &logger_api_dev.init);
	
	wp_logger_free_q(&logger_api_dev.wp_event_list);
	wp_logger_free_q(&logger_api_dev.wp_event_free_list);

	if (logger_api_dev.cdev) {
		wanpipe_cdev_free(logger_api_dev.cdev);
		wan_free(logger_api_dev.cdev);
		logger_api_dev.cdev=NULL;
	}
}

#if defined(__LINUX__)
EXPORT_SYMBOL(wp_logger_level_default);
EXPORT_SYMBOL(wp_logger_level_te1);
EXPORT_SYMBOL(wp_logger_level_hwec);
EXPORT_SYMBOL(wp_logger_level_tdmapi);
EXPORT_SYMBOL(wp_logger_level_fe);
EXPORT_SYMBOL(wp_logger_level_bri);
EXPORT_SYMBOL(wp_logger_input);
#endif
/****** End ****************************************************************/
