#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/proc_fs.h>
#include <net/ip.h>
#if defined(CONFIG_IPV6)
#include <net/ipv6.h>
#endif
extern struct proc_dir_entry *realtek_proc;

static int enable_skb_prio_assignment = 1;
int skb_prio2txdesc_map[8] = {0, 0, 0, 0, 0, 0, 0, 1};

#if 0
static void skb_debug(const char* data)
{
#define NUM2PRINT 64
        int i;
        for (i=0; i<NUM2PRINT; i++)
        {
                printk("%02X  ",data[i]&0xFF);
                if(i%16==15)
                        printk("\n");
                else if(i%8==7)
                        printk("  ");
        }
        printk("\n");
}
#endif

void skb_prio_assignment(struct sk_buff *skb) {
	struct tcphdr *th = NULL;
	struct iphdr *iph = NULL;

#if defined(CONFIG_IPV6)
	struct ipv6hdr *ipv6h = NULL;
#endif

	if (!enable_skb_prio_assignment)
		return;
	
	switch (skb->protocol) {
		case htons(ETH_P_IP):
			
			iph = ip_hdr(skb); 

			if (iph->protocol == htons(IPPROTO_TCP)) {
				th = (struct tcphdr *)((unsigned char *)iph + (iph->ihl << 2));
				if(th->ack && !th->syn && !th->fin && !th->rst && (iph->tot_len <= (iph->ihl + th->doff) << 2) ) {
					skb->priority = 7;
				}
			}
			break;
			
#if defined(CONFIG_IPV6)	
		case htons(ETH_P_IPV6):
			
			ipv6h = ipv6_hdr(skb);

			if (ipv6h->nexthdr == htons(IPPROTO_TCP)) {
				th = (struct tcphdr *)((unsigned char *)ipv6h + sizeof(struct ipv6hdr));
				if(th->ack && !th->syn && !th->fin && !th->rst && ipv6h->payload_len <= (th->doff << 2) ) {
					skb->priority = 7;
				}				
			}
			break;
#endif	
		default:
			//do nothing
			;
	}
}
EXPORT_SYMBOL(skb_prio_assignment);

static void show_usage(void){
	printk("	e [0/1] : enable/disable SKB Priority Assignment\n");
	printk("	m [desc_id desc_id desc_id ...] : set NIC tx desc prio map, fill 8 numbers in a row\n");
}

static int skb_prio_assignment_write_proc(struct file * file, const char __user * userbuf, size_t count, loff_t * off) {	  
	char buf[32];
	int len;
	int enabled;
	int map[8];
	int i;
	
	len = min(sizeof(buf), count);
	if (copy_from_user(buf, userbuf, len))
		return -E2BIG;
		
	if (strncmp(buf, "help", 4) == 0) {
		show_usage();
	} else if (strncmp(buf, "e ", 2) == 0) {
		if(1==sscanf(buf, "e %d", &enabled)){
			enable_skb_prio_assignment = enabled;
			
			printk("%s skb prio assignment\n", enable_skb_prio_assignment? "Enable":"Disable");
		}
		else{
			goto ERROR_PARA;
		}
	} else if (strncmp(buf, "m ", 2) == 0) {
		if (8 == sscanf(buf, "m %d %d %d %d %d %d %d %d", 
							&map[0], &map[1], &map[2], &map[3], 
							&map[4], &map[5], &map[6], &map[7])) 
		{
			printk("Set SKB priority to NIC TX desc ID map to:\n");
			for (i = 0 ; i < 8; i++) {
				skb_prio2txdesc_map[i] = map[i];
				printk("%d ", skb_prio2txdesc_map[i]);
			}
			printk("\n");
			
		} else{
			goto ERROR_PARA;
		}
	
	} else {
		goto ERROR_PARA;
	}
	return count;
	
ERROR_PARA:
	printk("error parameter...\n");
	show_usage();
	return -EPERM;
}

static int skb_prio_assignment_read_proc(struct seq_file *f, void *data)
{	
	int i;
	
    seq_printf(f, "SKB Priority Assignment: %s\n",  enable_skb_prio_assignment ? "Enabled" : "Disabled");
	seq_printf(f, "SKB Priority to NIC TX Desc ID map:\n");
	for (i = 0; i < 8; i++) {
		seq_printf(f, "%d ", skb_prio2txdesc_map[i]);
	}
	seq_printf(f, "\n");

    return 0;
}

static int read_proc_open_skb_prio_assignment(struct inode *inode, struct file *file) {
    return(single_open(file, skb_prio_assignment_read_proc, NULL));                                                                                                                                                        
}

static ssize_t write_proc_skb_prio_assignment(struct file *file, const char __user * userbuf, size_t count, loff_t * off) {                                                                                                
    return skb_prio_assignment_write_proc(file, userbuf, count, NULL);                                                                                                                                                     
}

static struct file_operations fops_proc_skb_prio_assignment = {
    .open     = read_proc_open_skb_prio_assignment,   
    .read     = seq_read,
    .llseek   = seq_lseek,
    .release  = single_release,    
    .write    = write_proc_skb_prio_assignment,                                                                                                                                                                            
};

static struct proc_dir_entry *skb_prio_assignment_proc=NULL;

static int skb_prio_proc_init(void) {
	if (!realtek_proc) {
		printk("Realtek SKB Priority Assignment, create proc failed, root dir not found\n");	
		return -1;
	}
	
	skb_prio_assignment_proc = proc_create_data("skb_prio_assignment", 0644, realtek_proc, &fops_proc_skb_prio_assignment, NULL);	
	if (!skb_prio_assignment_proc) {
		printk("Realtek SKB Priority Assignment, create proc failed!\n");
	}

	return 0;
}

static int skb_prio_proc_clean(void) {
	if (realtek_proc) {
		remove_proc_entry(skb_prio_assignment_proc, realtek_proc);
	}
	return 0;
}


static int __init rtk_skb_prio_init(void) {
	skb_prio_proc_init();
	return 0;
}

static int __exit rtk_skb_prio_exit(void) {
	skb_prio_proc_clean();
	return 0;
}

module_init(rtk_skb_prio_init);
module_exit(rtk_skb_prio_exit);