#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
//#include <linux/smp_lock.h>
#include <linux/cdev.h> 
#include <linux/spinlock.h>
#include <linux/proc_fs.h>
#include <linux/err.h>
#include <linux/syscalls.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/skbuff.h>
#include <linux/atmdev.h>
#include <linux/atm.h>
#include "xdsl_ctrl.h"
#include "xdsl_ctrl_api.h"

#define		WAITING			0
#define		RUNNING			1


extern int CheckTPScount_Flag;
extern void enable_ptm(void);
extern void disable_ptm(void);
extern void slave_bonding_rx_fifo_full(void);
extern void slave_bonding_rx_fifo_check(void);

static char xdsl_thread = 0;
unsigned char cmd_data_buffer[ DSL_CMD_POOL_SIZE ][ DSL_MAX_BUF_SIZE ];

xdsl_cmd_pool cmd_pool;
int xdsl_kthread_pid;
spinlock_t rx_lock;

extern cmd_t cmdTable[];

static DECLARE_WAIT_QUEUE_HEAD(xdsl_event_waitqueue);

extern int xdsl_ctrl_tx_final(uint8 devnum, uint8 category, uint8 protocol, uint32 command,
                              uint32 arg, uint32 argsize, uint32 ret, uint32 seg_num);
extern int XDSLCommand(unsigned int command, unsigned long inarg);
extern int PTMCommand(unsigned int command, unsigned long inarg);

static int xdsl_kthread(void* rx_data)
{
	unsigned char category=0;
	unsigned int flags;
	int index=0, ret=0, seg_num=0, size;
	xdsl_cmd *command;
	xdsl_arg *argument;

	DECLARE_WAITQUEUE(wait, current);			   /* wait queue.entry */
//	daemonize("xdsl_kthread");

	allow_signal(SIGKILL);
	add_wait_queue(&xdsl_event_waitqueue, &wait); 
					
	for (;;){ 
		if (xdsl_thread == WAITING){
		    set_current_state(TASK_INTERRUPTIBLE); /* kthread:interruptible */
		    schedule();							   /* CPU */
			if (signal_pending(current)) {		   /* SIGKILL signal */
				printk("xdsl_kthread: received a SIGKILL signal\n");
				break;
			}
		}	
		
		index = cmd_pool.start;
		command = &cmd_pool.data[index];

		switch(command->protocol)
		{
			case XDSL_CMD_SET:
				ret = XDSLCommand(command->cmdId, command->argp);
				break;

			case XDSL_CMD_GET:
				ret = XDSLCommand(command->cmdId, command->argp);
				if (command->category == CTRL_2DSL_GET){
					category = CTRL_2SOC_RET;
				} else if (command->category == CTRL_2MASTER_GET){
					category = CTRL_2SLAVE_RET;
				}

				if (command->argp){
					argument = (xdsl_arg*)command->argp;
					size = argument->argsize;
					seg_num = 0;			

					while (size>0){
						if (size > DSL_RXBUF_MAX_SIZE){

							xdsl_ctrl_tx_final(category, XDSL_CMD_DATA, command->devnum, 
    						                   command->cmdId,	argument->arg, 
											   DSL_RXBUF_MAX_SIZE, ret, seg_num);
							size -= DSL_RXBUF_MAX_SIZE;
							seg_num++;
						} else {
							xdsl_ctrl_tx_final(category, XDSL_CMD_DATA_EOF, 
								               command->devnum, command->cmdId, 
											   argument->arg, size, ret, seg_num);
							break;
						}
					}	
				} else {
					xdsl_ctrl_tx_final(category, XDSL_CMD_DATA_EOF, 
									command->devnum, command->cmdId, 0, 0, ret, 0);
				}
				break;

#if defined(CONFIG_PTM_BONDING_MASTER)				
			case XDSL_ERB_SET:
				erb_printk("Process XDSL_ERB_SET...\n");
				erb_dump_rxbuf(command->buf, command->argument.argsize);
				re865x_send_slave_ERB(command->buf, command->argument.argsize);
				kfree(command->buf);
				break;
			case SLAVE_XDSL_UP:
				enable_ptm();
				break;
			case SLAVE_XDSL_DOWN:
				disable_ptm();
				break;
			case SLAVE_RX_FIFO_FULL:
				slave_bonding_rx_fifo_full();
				break;
			case SLAVE_RX_FIFO_CHECK:
				slave_bonding_rx_fifo_check();
				break;
#endif

#if defined(CONFIG_PTM_BONDING_SLAVE)
			case SLAVE_TPSTC_RX_POLLING:
				CheckTPScount_Flag = 1;
				break;
#endif

			case PTM_CMD_SET:
				ret = PTMCommand(command->cmdId, command->argp);
				break;

			case PTM_CMD_GET:
				ret = PTMCommand(command->cmdId, command->argp);
				if (command->category == CTRL_2DSL_GET){
					category = CTRL_2SOC_RET;
					xdsl_ctrl_tx_final(category, PTM_CMD_DATA, 
					command->devnum, command->cmdId, 0, 0, ret, 0);
				}
		
				break;
			
			default:
				break;
		}

		spin_lock_irqsave(&rx_lock, flags);
		cmd_pool.start++;
		cmd_pool.counter--;

		if (cmd_pool.start == DSL_CMD_POOL_SIZE){
			cmd_pool.start = 0;	
		}

		if (cmd_pool.counter == 0){
			xdsl_thread = WAITING;
		}
		spin_unlock_irqrestore(&rx_lock, flags);

		xdsl_ctrl_ret(command->protocol);
	}	
																											   
	set_current_state(TASK_RUNNING);				 /* kthread running */
	remove_wait_queue(&xdsl_event_waitqueue, &wait); /* kthread wait queue */

	return 0; 
}

static int xdsl_kthread_init(void)
{   
	xdsl_kthread_pid = kernel_thread(xdsl_kthread, NULL, CLONE_FS | 
					CLONE_FILES | CLONE_SIGHAND | SIGCHLD); /* ....kthread */
	   
	if (xdsl_kthread_pid > 0) {
		printk("Created xdsl_kthread PID = %d\n", xdsl_kthread_pid);
	} else {
		printk("Failed to create xdsl_kthread\n");
	}			    

	cmd_pool.start = 0;
	cmd_pool.tail = 0;
	cmd_pool.counter = 0;

	printk("xdsl_kthread module loaded\n");
	return 0; 
}

void xdsl_ctrl_rx_final(void *data, uint32 pkt_len)
{
	int i, size, index=0;
	ctrl_pkt_t *ctrlp;
	ctrlp = get_ctrl_pkt(data);

	i = cmd_pool.tail;
	cmd_pool.data[i].devnum = ctrlp->devnum;
	cmd_pool.data[i].cmdId = ntohl((unsigned int) ctrlp->command);
	cmd_pool.data[i].protocol = ctrlp->protocol;
	cmd_pool.data[i].category = ctrlp->category;
	
/*===================== Command/Webpage ============================*/
	if (ctrlp->protocol==XDSL_CMD_SET || ctrlp->protocol==XDSL_CMD_GET){
		if (ctrlp->command==RLCM_UserSetDslData || ctrlp->command==RLCM_UserGetDslData){
			cmd_pool.data[i].argp = (unsigned long) &cmd_pool.data[i].argument;
			cmd_pool.data[i].argument.argsize = ntohl(ctrlp->argsize);
			cmd_pool.data[i].message.msg = ntohl(ctrlp->msg);
			memcpy(cmd_data_buffer[i], (char*)ctrlp->arg, DSL_ARG_MAX_SIZE );
			cmd_pool.data[i].message.intVal = (int*)cmd_data_buffer[i];
			cmd_pool.data[i].argument.arg = (int)(&cmd_pool.data[i].message);
		} else	if ((ctrlp->command>=ADSL_CMD_BASE && ctrlp->command<=ADSL_CMD_END) || 
  				    (ctrlp->command>=VDSL_CMD_BASE && ctrlp->command<=VDSL_CMD_END)){
			if (ctrlp->argsize==0){
				cmd_pool.data[i].argp = 0;
				cmd_pool.data[i].argument.arg = 0;
				cmd_pool.data[i].argument.argsize = 0;
			} else {
				cmd_pool.data[i].argp = (unsigned long) &cmd_pool.data[i].argument;
				index = 0;
				for (size=0; size<ctrlp->argsize; size++){
					cmd_data_buffer[cmd_pool.tail][index] = ctrlp->arg[index];
					index++;
				}	
				cmd_pool.data[i].argument.arg = (unsigned long) &cmd_data_buffer[cmd_pool.tail];
				cmd_pool.data[i].argument.argsize = ntohl(ctrlp->argsize);
			}
		}
	}
	else if (ctrlp->protocol==XDSL_ERB_SET) {
		erb_printk("Receive ERB from slave\n");
		erb_dump_ctrlp(ctrlp);
		cmd_pool.data[i].argument.argsize = ntohl(ctrlp->argsize);
		cmd_pool.data[i].buf = kmalloc(cmd_pool.data[i].argument.argsize, GFP_KERNEL);
		if (cmd_pool.data[i].buf == NULL) {
			printk("alloc memory for cmd_pool.data[%d].buf fail\n", i);
			free_ctrl_pkt(data);
			return;
		}
		memcpy(cmd_pool.data[i].buf, ctrlp->rxbuf, cmd_pool.data[i].argument.argsize);
	}
	else if (ctrlp->protocol==PTM_CMD_SET || ctrlp->protocol==PTM_CMD_GET){
		index = 0;
		for (size=0; size<ctrlp->argsize; size++){
			cmd_data_buffer[cmd_pool.tail][index] = ctrlp->arg[index];
			index++;
		}	
		cmd_pool.data[i].argp =  (unsigned long) &cmd_data_buffer[cmd_pool.tail];
	} 
	else if (ctrlp->protocol==SLAVE_XDSL_UP || ctrlp->protocol==SLAVE_XDSL_DOWN){
		if (ctrlp->protocol==SLAVE_XDSL_UP)
			printk("SLAVE XDSL UP\n");
		else
			printk("SLAVE XDSL DOWN\n");
		
	} 
	else if (ctrlp->category==CTRL_SLAVE_EXECEPTION){
		if (ctrlp->protocol==SLAVE_RX_FIFO_FULL)
			printk("SLAVE RX FIFO FULL\n");
		else if (ctrlp->protocol==SLAVE_RX_FIFO_CHECK)
			printk("SLAVE RX FIFO CHECK\n");
	} else 	{ 
		printk("invalid dsl command, do nothing!!\n");
		free_ctrl_pkt(data);
		return;
	}

	free_ctrl_pkt(data);

	spin_lock(&rx_lock);
	cmd_pool.counter++;
	cmd_pool.tail++;
	xdsl_thread = RUNNING;
	spin_unlock(&rx_lock);

	if (cmd_pool.tail == DSL_CMD_POOL_SIZE){
		cmd_pool.tail = 0;
	}

	if (cmd_pool.counter >= DSL_CMD_POOL_SIZE){
		printk("xdsl command pool is full!!\n");
	}

	wake_up_interruptible(&xdsl_event_waitqueue); /* wake up kthread */
}


void xdsl_ctrl_receive( void *recv_pkt, unsigned long size)
{
	xdsl_ctrl_rx_final( recv_pkt, size );
}


void xdsl_ctrl_rx_init(void)
{
	xdsl_kthread_init();
}