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

#ifdef __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_list args;	\
	\
	memset(logger_event, 0x00, sizeof(*logger_event));	\
	\
	logger_event->logger_type = logger_type;	\
	logger_event->event_type = evt_type;	\
	\
	va_start(args, format);	\
	wp_vsnprintf(logger_event->data, sizeof(logger_event->data), format, args);	\
	va_end(args);	\
	\
	WAN_LOGGER_SET_TIME_STAMP(logger_event)	\
}

#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;	\
}


/*=================================================================
 * 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;
	wan_taskq_t 		wp_logger_task;

	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;


static wp_logger_api_dev_t	logger_api_dev;
static wanpipe_cdev_ops_t	wp_logger_api_fops;


/*=================================================================
 * 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;
	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, new_level: 0x%08X\n",
		__FUNCTION__, 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)=%i\n",
			logger_api_dev.name, tmp_usr_logger_api.cmd, 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;
}


#if defined(__LINUX__)
# if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
static void wp_logger_task_func (void *not_used)
# else
static void wp_logger_task_func (struct work_struct *not_used)
# endif
#elif defined(__WINDOWS__)
static void wp_logger_task_func (IN PKDPC Dpc, IN PVOID not_used, IN PVOID SystemArgument1, IN PVOID SystemArgument2)
#else
static void wp_logger_task_func (void *not_used, int arg)
#endif
{
	wp_logger_wakeup_api();
}

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;
}

int wp_logger_repeating_message_filter(u_int32_t logger_type, u_int32_t evt_type, const char * fmt, ...)
{
    va_list	va_arg_list;
	
	/* Filter out repeating messages. */
	static char previous_error_message[WP_MAX_NO_BYTES_IN_LOGGER_EVENT];
	static char current_error_message[WP_MAX_NO_BYTES_IN_LOGGER_EVENT];
	static char tmp_message_buf[WP_MAX_NO_BYTES_IN_LOGGER_EVENT];
	static u32	repeating_error_message_counter = 0;
	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 */
		memset(previous_error_message, 0x00, sizeof(previous_error_message));
		repeating_error_message_counter = 0;
		return 0;
	}

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

	/* the paramter list must start at fmt, not at evt_type */
    va_start (va_arg_list, fmt);
	
	wp_vsnprintf(current_error_message, sizeof(current_error_message) - 1,
		(const char *)fmt, va_arg_list);
		
	va_end (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, current_error_message);

				/* and say how many times it was repeated. */
				wp_snprintf(tmp_message_buf, sizeof(tmp_message_buf),
					"* Message repeated %d times.\n", repeating_error_message_counter);
				wp_logger_input(logger_type, evt_type, 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),
				"* Message repeated %d times.\n", repeating_error_message_counter);
			wp_logger_input(logger_type, evt_type, 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;
	}
}

/*=================================================================
 * 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;

	/* Independently of Logger Device state, write the message into log file. */

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

 #ifdef __WINDOWS__
	/* Write the message to wanpipelog.txt */
	vOutputLogString(fmt, va_arg_list);
 #else
	/* Write the message to kernel log. */
	vprintk(fmt, va_arg_list);
 #endif

    va_end (va_arg_list);

	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);
			
			wp_logger_push_event(skb);
			
			WAN_TASKQ_SCHEDULE((&logger_api_dev.wp_logger_task));
		}
	}
}

/* This function during module load - the 'logger_api_dev' structure
 * is initialized for 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_TASKQ_INIT((&logger_api_dev.wp_logger_task),0,wp_logger_task_func, &logger_api_dev);
	
	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)
{
	int err;

	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;
	}

	err = WAN_TASKQ_STOP((&logger_api_dev.wp_logger_task));

	DEBUG_EVENT("%s\n", err ? "Logger TASKQ Successfully Stopped" : "Logger TASKQ Not Running");

	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);
EXPORT_SYMBOL(wp_logger_repeating_message_filter);
#endif
/****** End ****************************************************************/
