/*
 * Copyright (c) 2008 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology Limited and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * Author: Vladimir Minkov
 */
 
#include "config.h"
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
// remap_pfn_range
#include <linux/mm.h>
#include <linux/sched.h>

//include <linux/modversions.h>
#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33)
#include <generated/autoconf.h>
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)
#include <linux/autoconf.h>
#else
#include <linux/config.h>
#endif 

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
#include <asm/io.h>
#else
#include <linux/io.h>
#endif
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
#include <asm/uaccess.h>
#else
#include <linux/uaccess.h>
#endif

#include "../dagcompat.h"
#include "dagnew.h"
#include "dagpbm.h"
#include "vdag.h"
#include "dagpci.h" /* for PCI_DEVICE_ID_VDAG */
#include "../dagdebug.h"

#include <linux/timer.h>

extern const char* const kDagReleaseVersion;

#define MAX_PARAM_SIZE 128
#define VDAG_DEFAULT_MEMSIZE (32*1024*1024)

static int	vdag_major;
static int	vdag_iom_major;

static int	gfporder = 10;
DAG_MODULE_PARAM(gfporder, int, 0);
MODULE_PARM_DESC(gfporder, "Order of memory allocations, default 10");


static char	a_dsize[MAX_PARAM_SIZE], *dsize = a_dsize;
static int  vdag_mem_size = VDAG_DEFAULT_MEMSIZE;
DAG_MODULE_PARAM_STRING(dsize, a_dsize, sizeof(a_dsize), 0);
MODULE_PARM_DESC(dsize, "Memory to allocate per vdag. KB, MB, GB suffixes, default 32MB");

/* Driver global array of data structures holding state information per card */
static vdag_softstate_t vdag_array[VDAG_MAX_BOARDS];


extern struct file_operations	vdag_fops;
//extern struct file_operations	vdag_mem_fops;
extern struct file_operations	vdag_iom_fops;
//extern struct file_operations	vdag_arm_fops;


static unsigned int timer_shutdown;
static unsigned int timer_delay;
static void vdag_timer_handler(unsigned long);
static int vdag_stream_start(vdag_softstate_t *sc, int stream_num);
static void * vdag_monitor(void * unused);
static struct timer_list vdag_timer = TIMER_INITIALIZER(vdag_timer_handler, 0, 0);

/*
  a5 ff 00 00 a5 ff 00 00  a0 00 02 50 a0 00 02 50  |...........P...P|
00000910  e0 00 64 10 e0 00 64 10  f0 00 35 10 f0 00 35 10  |..d...d...5...5.|
00000920  08 01 57 00 08 01 57 00  00 28 00 08 00 28 00 08  |..W...W..(...(..|
00000930  00 02 4c 10 00 02 4c 10  00 12 50 00 00 12 50 00  |..L...L...P...P.|
00000940  08 12 50 00 08 12 50 00  00 13 58 00 00 13 58 00  |..P...P...X...X.|
00000950  20 14 6a 00 20 14 6a 00  00 31 11 20 00 31 11 20  | .j. .j..1. .1. |
00000960  ff ff ff 00 ff ff ff 00  00 00 00 00 00 00 00 00  |................|
*/

#include <dagreg.h>
			 
/* 
 * Prior to Bug #4173 closing, dagconfig utility would panic if there was
 * no ROM Controller. This meant that vdag driver had to include one even
 * though it is not really emulated so produces a lot of warnings.
 *
 * With new versions of dagconfig there is no panic so vdag does not need
 * to export the ROM controller.
 *
 * Set this to 1 if needed for backwards compatibility with older guest
 * software versions.
 */
#define REQUIRE_REG_ROM 0

static 
dag_reg_t vdag_enum_table [] = 
{
		/* Enum table Start*/
		{
			0xffa5,
			DAG_REG_START,
			0,
			0,
		},
		{
			0x0080,
			DAG_REG_GENERAL, /* general registers */
			0,
			0,
		},
#if REQUIRE_REG_ROM /* Backwards compatibility */
  		{
			0x0158,
			DAG_REG_ROM, /* ROM */
			0,
			0,
		},
#endif
		{
			0x2000,
			DAG_REG_PBM, /* PBM */
			0,
			3,
		},
		{
			0x7ff8,
			DAG_REG_STREAM_FTR,
			0,
			0,
		},		
		{
			0xffff,
			DAG_REG_END,
			0,
			0,
		}	
};

int 
vdag_drb_init(size_t size, void **drb_address){
	struct page *page;
	int c;

	*drb_address = vmalloc(size);
	if( *drb_address == NULL ) {
		DAG_ERROR("vdag: DRB memory allocation FAIL\n" );
		return -1 ;
	}


	for (c = 0; c < size; c+=PAGE_SIZE) {
	    page = vmalloc_to_page(*drb_address + c);
	    DAG_DEBUG("vdag_drb_init: offt 0x%x page 0x%lx flags 0x%lx mapping 0x%lx mapcount %d count %d\n",c, page, page->flags, page->mapping, page->_mapcount, page->_count);
	}
	memset(*drb_address, 0, size); 
	/* copy the enum table */
	memcpy(*drb_address + 0x900, vdag_enum_table, sizeof(vdag_enum_table));
	/* write PBM setup */
	*(uint32_t *)(*drb_address + 0x2000)= 0x50000220;
	/* stream counts */
	*(uint32_t *)(*drb_address + 0x200c)= 0x00010001;

	for( c = 0; c < DAG_STREAM_MAX; c++) {
		/* set Paused, clear SYNC_L2R */
		*(uint32_t *)(*drb_address + 0x2000 + (c + 1) * 0x40 + 0x30) = 1;
	}

	/* write stream feature setup */
	*(uint32_t *)(*drb_address + 0x7ff8)= 0x04000001;
	*(uint32_t *)(*drb_address + 0x7ffc)= 0x00000008;
	for( c = 0; c < DAG_STREAM_MAX; c++) {
		*(uint32_t *)(*drb_address + 0x800c + c*0x10)= 0x00000080;
	}

	return 0; 
}

static void vdag_membuf_free(vdag_softstate_t *sc)
{
	int cnt = 0;
	struct page *pg;

	if (!sc->page_list)
		return;

	while (sc->page_list[cnt]) {

		/* Re-merge compound allocations */
		for (pg = sc->page_list[cnt]; 
		     pg < sc->page_list[cnt] + (1 << sc->order); pg++) {
			if (pg != sc->page_list[cnt]) {
				atomic_dec(&pg->_count);
			}			
		}

		__free_pages(sc->page_list[cnt], sc->order);
		cnt++;
	}
	vfree(sc->page_list);
	sc->page_list = NULL;
	sc->membuf_size = 0;
	return;
}

static int vdag_membuf_alloc(uint32_t size, int req_node, vdag_softstate_t *sc)
{
	int nr_recs, cnt = 0;
	int online_node;
	struct page *pg;
	
	gfporder = min (gfporder, MAX_ORDER - 1);

	sc->order = get_order(size);
	sc->order = min(sc->order, gfporder);

	nr_recs = (size >> (sc->order + PAGE_SHIFT)) + 1;

	if(sc->page_list) {
		DAG_ERROR("Allocation is called over already allocated area!\n");
		return -EFAULT;
	}
	
	if(!size) {
		return 0;
	}
	/* Correct NUMA node to make sure it is online */
	online_node = dag_correct_node(req_node);

	/* If user specified a non default and a non online node - Error */
	if ((req_node != DAG_DEFAULT_NODE) && (online_node != req_node)) {
		DAG_ERROR("Invalid node %d is specified\n", req_node);
		return -EINVAL;
	}

	sc->page_list = vmalloc(nr_recs * sizeof(void *));
	if (!sc->page_list)
		return -ENOMEM;
	memset(sc->page_list, 0, nr_recs * sizeof(void *)); 


	size = DAG_ALIGN(size, PAGE_SIZE << sc->order);
	sc->membuf_size = size;
	while (size) {
		sc->page_list[cnt] = dag_alloc_pages(online_node, 
					GFP_HIGHUSER | __GFP_NOWARN, 
					sc->order);
		/* Failed to get memory */
		if (!sc->page_list[cnt])
			goto out_nomem;

		/* Failed to get memory on given node */
		if ((req_node != DAG_DEFAULT_NODE)
		    && (online_node != dag_page_to_nid(sc->page_list[cnt])))
			goto out_nomem;

		/* Split compound allocations for page fault handler.  Unfortunately there are no 
		interfaces in kernel to do this. So we use very low level things here (don't use get_page - 
		this is for already split pages only ). */
		for (pg = sc->page_list[cnt]; 
		     pg < sc->page_list[cnt] + (1 << sc->order); pg++) {
			if (pg != sc->page_list[cnt]) {
				atomic_inc(&pg->_count);
			}			
		}

		cnt++;
		size -= PAGE_SIZE << sc->order;
	}
	DAG_DEBUG("ALLOC done !\n");
	
	return 0;

out_nomem:
	vdag_membuf_free(sc);
	return -ENOMEM;
}

static struct page* vdag_get_page(vdag_softstate_t *sc, unsigned long pgoff)
{
	unsigned long entry_nr, entry_mask, page_nr;
 	struct page *page;

	/* Error */
	if (!sc || !sc->page_list)
		return NULL;
	if (pgoff > (sc->membuf_size >> PAGE_SHIFT))
		return NULL;

	entry_mask = (1 << sc->order) - 1;
	entry_nr = pgoff >> sc->order;
	page_nr = pgoff & entry_mask;
	page = nth_page(sc->page_list[entry_nr],page_nr);
	return page;
}


static void vdag_get_mem_info(vdag_softstate_t *sc, dag_meminfo_t *mem_info)
{
	uint32_t	nid;
	uint64_t	pos;
	int		entry_nr = 0;

	memset(mem_info,0x0, sizeof(dag_meminfo_t));
	pos = 0;

	mem_info->page_size = PAGE_SIZE;

	/* Error */
	if (!sc || !sc->page_list)
		return;

	while (sc->page_list[entry_nr]) {
		nid = dag_page_to_nid(sc->page_list[entry_nr]);

		if (nid >= DAG_MAX_NODES)
			DAG_ERROR("DAG Error returned node Id %u is out of ragne!\n",nid);
		else
			mem_info->nid_pages[nid] += 1 << sc->order;
		entry_nr++;
	}
}



#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)

struct vm_fault {
	unsigned int flags;
	pgoff_t pgoff;
	void __user *virtual_address;
	struct page *page;
};
#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23) */

static int
vdag_iom_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
	vdag_softstate_t	*sc = vma->vm_private_data;
	struct page *page;
	void *pageptr;

	DAG_DEBUG("vdag_iom_fault: vmf vaddr 0x%p pgoff 0x%lx\n", vmf->virtual_address, vmf->pgoff);

	DAG_DEBUG("vdag_iom_fault: vma start 0x%lx end 0x%lx flags 0x%lx pgoff 0x%lx\n", vma->vm_start,  vma->vm_end, vma->vm_flags, vma->vm_pgoff);

	if((vmf->pgoff << PAGE_SHIFT) > sc->drb_regs_size)
		return VM_FAULT_SIGBUS;
	
	pageptr = sc->drb_regs + (vmf->pgoff << PAGE_SHIFT);
	page = vmalloc_to_page(pageptr);

	DAG_DEBUG("vdag_iom_fault: page 0x%lx flags 0x%lx mapping 0x%lx mapcount %d count %d before\n", page, page->flags, page->mapping, page->_mapcount, page->_count);
	get_page(page);
	DAG_DEBUG("vdag_iom_fault: page 0x%lx flags 0x%lx mapping 0x%lx mapcount %d count %d after\n", page, page->flags, page->mapping, page->_mapcount, page->_count);
	
	vmf->page = page;
	return 0;
}

static int
vdag_mem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
	vdag_softstate_t	*sc = vma->vm_private_data;
	struct page *page;

	DAG_DEBUG("vdag_mem_fault: vmf vaddr 0x%p pgoff 0x%lx\n", vmf->virtual_address, vmf->pgoff);

	DAG_DEBUG("vdag_mem_fault: vma start 0x%lx end 0x%lx flags 0x%lx pgoff 0x%lx\n", vma->vm_start,  vma->vm_end, vma->vm_flags, vma->vm_pgoff);

	if((vmf->pgoff << PAGE_SHIFT) > sc->membuf_size)
		return VM_FAULT_SIGBUS;
	
	page = vdag_get_page(sc, vmf->pgoff);
	if (!page)
		return -EINVAL;

	DAG_DEBUG("vdag_mem_fault: page 0x%lx flags 0x%lx mapping 0x%lx mapcount %d count %d before\n", page, page->flags, page->mapping, page->_mapcount, page->_count);
	get_page(page);
	DAG_DEBUG("vdag_mem_fault: page 0x%lx flags 0x%lx mapping 0x%lx mapcount %d count %d before\n", page, page->flags, page->mapping, page->_mapcount, page->_count);
	vmf->page = page;
	return 0;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)

static inline struct page*
vdag_iom_nopage(struct vm_area_struct *vma,
		   unsigned long address,
		   int *type)
{
	struct vm_fault vmf;
	int ret;

	vmf.pgoff = ((address - vma->vm_start) >> PAGE_SHIFT) + vma->vm_pgoff;
	vmf.virtual_address = (void __user *)address;
	ret = vdag_iom_fault(vma, &vmf);
	if (ret)
		return NOPAGE_SIGBUS;
	*type = VM_FAULT_MINOR;
	return vmf.page;
}

static inline struct page*
vdag_mem_nopage(struct vm_area_struct *vma,
		   unsigned long address,
		   int *type)
{
	struct vm_fault vmf;
	int ret;

	vmf.pgoff = ((address - vma->vm_start) >> PAGE_SHIFT) + vma->vm_pgoff;
	vmf.virtual_address = (void __user *)address;
	ret = vdag_mem_fault(vma, &vmf);
	if (ret)
		return NOPAGE_SIGBUS;
	*type = VM_FAULT_MINOR;
	return vmf.page;
}

static struct vm_operations_struct vdag_iom_vm_ops = {
	.nopage = vdag_iom_nopage,
};

static struct vm_operations_struct vdag_mem_vm_ops = {
	.nopage = vdag_mem_nopage,
};

#else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23) */

static struct vm_operations_struct vdag_iom_vm_ops = {
	.fault = vdag_iom_fault,
};

static struct vm_operations_struct vdag_mem_vm_ops = {
	.fault = vdag_mem_fault,
};

#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23) */

static int
vdag_iom_mmap(struct file *fp, struct vm_area_struct * vma)
{
	vdag_softstate_t	*sc = fp->private_data;
//TODO:  We need a lock here but, hey this is first version 

	if(sc->drb_regs == NULL) {
		DAG_DEBUG("vdag%u: DRB initialization dag\n", sc->unit);
		sc->drb_regs_size = 64*1024;
		if ( vdag_drb_init(sc->drb_regs_size,&sc->drb_regs) != 0 )
		{
			return -EAGAIN;
		}
	}

	vma->vm_ops = &vdag_iom_vm_ops;
	vma->vm_flags |= VM_RESERVED;
	vma->vm_private_data = sc;
	return 0;
}


/* Main Memory mapping */

static int
vdag_mmap(struct file *fp, struct vm_area_struct * vma)
{
	vdag_softstate_t	*sc = fp->private_data;

	if(sc->page_list[0] == NULL) 
	{
		DAG_INFO("vdag%u: Memory buffer not initialized for vdag\n", sc->unit);
		{
			return -ENOMEM;
		}
	}

	vma->vm_ops = &vdag_mem_vm_ops;
	vma->vm_flags |= VM_RESERVED;
	vma->vm_private_data = sc;
	return 0;
}

static ssize_t
vdag_iom_write(struct file *fp, const char *buf, size_t count, loff_t *ppos)
{
	vdag_softstate_t	*sc = fp->private_data;
	size_t		remain;

	if(*ppos > sc->drb_regs_size)
		return 0;
	if((*ppos + count) > sc->drb_regs_size)
		count = sc->drb_regs_size - *ppos;
	if(count == 0)
		return 0;
	if((*ppos & 0x03) != 0)
		return -EFAULT;		/* only support word aligned access */
	if((count & 0x03) != 0)
		return -EFAULT;		/* only support word sized access */
	remain = copy_from_user(sc->drb_regs + *ppos, buf, count);
	*ppos += count - remain;
	if(remain == count)
		return -EINVAL;
	else
		return count-remain;
}

static ssize_t
vdag_iom_read(struct file *fp, char *buf, size_t count, loff_t *ppos)
{
	vdag_softstate_t	*sc = fp->private_data;
	size_t		remain;
	int error;

	error = 0;
	if(*ppos > sc->drb_regs_size)
		return 0;
	
	if(down_interruptible(&sc->lock_sem))
	{
		return  -ERESTARTSYS;
	}
		
	if((*ppos + count) > sc->drb_regs_size)
		count = sc->drb_regs_size - *ppos;
	
	if(count == 0)
	{
		error = 0;
	} else 
	
	if((*ppos & 0x03) != 0)
	{
		error = -EFAULT;		/* only support word aligned access 
						for v dag this is not required but we emulate even this */
	} else 
	
	if((count & 0x03) != 0)
	{
		error = -EFAULT;		/* only support word sized access */
	} else 
	{
		remain = copy_to_user(buf, sc->drb_regs + *ppos, count);
		*ppos += count - remain;
	
		if(remain == count) {
			error = -EINVAL;
		} else {
			error = count-remain;
		};
	} 
	
  	//vdag_iom_read_exit:
	up(&sc->lock_sem);
	return error;
}

static int
vdag_open(struct inode *inode, struct file *file)
{
	int		unit = VDAGUNIT(inode);
	//note the minor is actually a function  can be any digit to find out which minor we are 
	int		minor = VDAGMAJOR(inode);
	vdag_softstate_t	* sc;
	int 		error = 0;

	DAG_DEBUG("vdag_open i_rdev %d unit(minor card) %d minor(function-major) %d priv %p\n", inode->i_rdev, unit, minor, file->private_data);
	/* if file->private_data is not valid the first time we open a card , then we are not using devfs.
	 * This means that we must decode the minor number to attach the
	 * correct fops for the entry point.
	 */
	error = 0;

	if(!file->private_data && minor) 
	{
		/* check minor number is valid */
		/* if (minor >= DAGMINOR_MAX) {
			DAG_ERROR("dag%u: open: illegal minor %u\n", unit, minor);
			return -ENODEV;
		} 
		*/
		//NOTE we should not need this if we take care into the functions or assign different major and differnt operations per major 
		DAG_DEBUG("vdag: assign the correct file operations\n");    
		//TODO add for the other operations 
		//here minor is actuall the major for vdag 
		if ( minor == vdag_major ) {
			file->f_op = &vdag_fops;
		} else 
		{
			file->f_op = &vdag_iom_fops;
		}
	}

	sc = (vdag_softstate_t *)file->private_data;
	

	/* if file->private_data is not valid, then we are not using devfs.
	 * This means we need to check that the unit number is valid, and
	 * fill in private_data for other methods
	 */
	if (!sc) 
	{
		DAG_DEBUG("vdag%u: open: card %u number init the private_data \n", unit, unit);
		/* Unit(card)  spans too high */
		if(unit >= VDAG_MAX_BOARDS ) {
			DAG_ERROR("vdag%u: open: unit number too high, does not exist\n", unit);
			return  -ENODEV;
		}

		file->private_data = &vdag_array[unit];
		sc = (vdag_softstate_t *)file->private_data;
		//store the card number in the soft_state for easy access
		sc->unit = unit;
	};
	
	if(down_interruptible(&sc->lock_sem))
			return  -ERESTARTSYS;
	/* allocate drb space if the open on this card is for the first time */
	if(!sc->drb_regs) {
		sc->drb_regs_size = 64*1024;
		if ((error = vdag_drb_init(sc->drb_regs_size,&sc->drb_regs)))
			goto out_err;
	}

	if(!sc->page_list) {
		if ((error = vdag_membuf_alloc(vdag_mem_size, numa_node_id(), sc)))
			goto out_err;
	}
	
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	MOD_INC_USE_COUNT;
#endif
	up(&sc->lock_sem);
	return 0;

out_err:
	up(&sc->lock_sem);
	return error;
}

/* MUST hold lock_sem before calling */
static int
vdag_card_is_locked(struct file *fp)
{
	vdag_softstate_t *sc;
	vdag_lockstate_t *lock;
	
	if ( (!fp) || (!fp->private_data) ) {
		return -EINVAL;
	}
	
	sc = fp->private_data;
	lock = &sc->card_lock;

	if (lock->pid)
		return -EACCES;

	return 0;
}

/* MUST hold lock_sem before calling */
static int
vdag_mode_is_locked(struct file *fp, int mode)
{
	vdag_softstate_t *sc;
	vdag_lockstate_t *lock;
	
	if ( (!fp) || (!fp->private_data) || (mode >= DAG_LOCK_MODE_MAX) ) {
		return -EINVAL;
	}

	sc = fp->private_data;
	lock = &sc->mode_lock[mode];

	if (lock->pid)
		return -EACCES;

	return 0;
}

/* MUST hold lock_sem before calling */
static int
vdag_stream_is_locked(struct file *fp, int stream, int mode)
{
	vdag_softstate_t *sc;
	vdag_lockstate_t *lock;
	
	if ( (!fp) || (!fp->private_data) || (mode >= DAG_LOCK_MODE_MAX)) {
		return -EINVAL;
	}
	
	sc = fp->private_data;
	lock = &sc->stream_lock[stream][mode];

	if (lock->pid)
		return -EACCES;

	return 0;
}

/* MUST hold lock_sem before calling */
static int
vdag_card_lock(struct file *fp, int set)
{
	vdag_softstate_t *sc;
	vdag_lockstate_t *lock;
	int error, stream;

	if ( (!fp) || (!fp->private_data) ) {
		return -EINVAL;
	}
	
	sc = fp->private_data;
	lock = &sc->card_lock;

	if (set) {
		/* cant get card lock if any lower level locks held */

		/* test mode locks */
		if ( (error = vdag_mode_is_locked(fp, DAG_NORMAL_MODE)) )
			return error;

		if ( (error = vdag_mode_is_locked(fp, DAG_REVERSE_MODE)) )
			return error;

		/* test stream locks */
		for(stream = 0; stream < DAG_STREAM_MAX; stream++) {
			if ( (error = vdag_stream_is_locked(fp, stream, DAG_NORMAL_MODE)) )
				return error;
			
			if ( (error = vdag_stream_is_locked(fp, stream, DAG_REVERSE_MODE)) )
				return error;
		}
		
		if(lock->pid == 0) {
			lock->pid = current->pid;
			lock->tgid = current->tgid;
			lock->locked_file = fp;
		} else if (lock->pid == current->pid) {
			/* multiple acquire lock, fine */
		} else {
			return -EACCES;
		}

	} else { /* unlock */
		if( (lock->tgid == current->tgid) &&
		    (lock->locked_file == fp) )
		{	
			lock->pid = 0;
			lock->tgid = 0;
			lock->locked_file = 0;
		} else { /* someone elses lock */
			return -EACCES;
		}
	}

	DAG_DEBUG("dag_ioctl: unit %u card lock now held by %d (group %d), current pid %d (group %d)\n", sc->unit, lock->pid, lock->tgid, current->pid, current->tgid);

	return 0;
}

/* MUST hold lock_sem before calling */
static int
vdag_mode_lock(struct file *fp, int mode, int set)
{
	vdag_softstate_t *sc;
	vdag_lockstate_t *lock;
	int error, stream;

	if ( (!fp) || (!fp->private_data) || (mode >= DAG_LOCK_MODE_MAX) ) {
		return -EINVAL;
	}
	
	sc = fp->private_data;
	lock = &sc->mode_lock[mode];

	/* test card lock first */
	if ( (error = vdag_card_is_locked(fp)) )
		return error;

	if (set) {
		/* cant get card lock if any lower level locks held */

		/* test stream locks */
		for(stream = 0; stream < DAG_STREAM_MAX; stream++) {
			if ( (error = vdag_stream_is_locked(fp, stream, mode)) )
				return error;
		}
		
		if(lock->pid == 0) {
			lock->pid = current->pid;
			lock->tgid = current->tgid;
			lock->locked_file = fp;
		} else if (lock->pid == current->pid) {
			/* multiple aquire lock, fine */
		} else {
			return -EACCES;
		}

	} else { /* unlock */
		if( (lock->tgid == current->tgid) &&
		    (lock->locked_file == fp) )
		{	
			lock->pid = 0;
			lock->tgid = 0;
			lock->locked_file = 0;
		} else { /* someone elses lock */
			return -EACCES;
		}
	}

	DAG_DEBUG("dag_ioctl: unit %u %s mode lock now held by %d (group %d), current pid %d (group %d)\n", sc->unit, mode?"REVERSE":"NORMAL", lock->pid, lock->tgid, current->pid, current->tgid);

	return 0;
}

/* MUST hold lock_sem before calling */
static int
vdag_stream_lock(struct file *fp, int stream, int mode, int set)
{
	vdag_softstate_t *sc;
	vdag_lockstate_t *lock;
	int error;

	if ( (!fp) || (!fp->private_data) || (mode >= DAG_LOCK_MODE_MAX) || (stream >= DAG_STREAM_MAX) ) {
		return -EINVAL;
	}
	
	sc = fp->private_data;

	/* test card lock first */
	if ( (error = vdag_card_is_locked(fp)) )
		return error;

	/* test mode lock second */
	if ( (error = vdag_mode_is_locked(fp, mode)) )
		return error;

	/* can do stream op */

	lock = &sc->stream_lock[stream][mode];
	
	if (set) {
		if(lock->pid == 0) {
			lock->pid = current->pid;
			lock->tgid = current->tgid;
			lock->locked_file = fp;
		} else if ((lock->pid == current->pid)/* && (lock->locked_file == fp)*/){
			/* multiple aquire lock, fine */
		} else {
			return -EACCES;
		}

	} else {
		if( (lock->tgid == current->tgid) &&
		    (lock->locked_file == fp) )
		{	
			lock->pid = 0;
			lock->tgid = 0;
			lock->locked_file = 0;
		} else {
			return -EACCES;
		}
		if(!mode) sc->stream_stat[stream] &= ~(uint32_t)0x1;
	}

	DAG_DEBUG("dag_ioctl: unit %u stream %ld %s mode lock now held by %d (group %d), current pid %d (group %d)\n", sc->unit, stream, mode?"REVERSE":"NORMAL", lock->pid, lock->tgid, current->pid, current->tgid);

	return 0;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)
static long vdag_iom_unlocked_ioctl(struct file *fp, u_int cmd, u_long arg)
#else
static int vdag_iom_ioctl(struct inode *inode, struct file *fp, u_int cmd, u_long arg)
#endif
{
	vdag_softstate_t	*sc = fp->private_data;	
	int		error = 0;
	u_long		larg;
//	int		params[16];
	user_mem_t      mem_user;
	dag_meminfo_t	mem_info;
	daginf_t	info;
#if 0 
	duckinf_t	duckinf;
#endif 	
	int 	tmp_state;
	int 	stream_num;
    	int mode, op;
	int dev_code;
	int brd_rev;
	
	DAG_DEBUG("vdag%d: IOCTL 0x%08x\n", sc->unit, cmd);
	error = 0;
	if (_IOC_TYPE(cmd) != DAG_IOC_MAGIC) return -ENOTTY;
	if (_IOC_NR(cmd) > DAG_IOC_MAXNR) return -ENOTTY;
	if (_IOC_DIR(cmd) & _IOC_READ)
		error = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
	else if (_IOC_DIR(cmd) & _IOC_WRITE)
		error = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
	if (error) return -EFAULT;
	
	if(down_interruptible(&sc->lock_sem))
		{
			return  -ERESTARTSYS;
		};

	//store the cuurrent task state 
	//and put in uninterruptable state in case of dangerous operations 
	tmp_state = current->state;
	if( cmd == DAGIOCRESET ) 
	{
	    set_current_state(TASK_UNINTERRUPTIBLE);
	}

	switch(cmd) {
	case DAGIOCRESET:
		__get_user(larg, (int*)arg);
		DAG_INFO("vdag%d: IOCRESET called type %ld\n",
			       sc->unit, larg);
		error = 0;
		break;
#if 0 		
		switch(larg) {
		case DAGRESET_FULL:
			error = dag_reset(sc);
			break;
		case DAGRESET_REBOOT:
			//pci config resotore originaly 
			error = 0;
			break;
		case DAGRESET_DUCK:
			duck_init(sc);	/* restart duck */
			break;
		default:
			DAG_ERROR("vdag%d: Unknown IOCRESET type %ld\n",
			       sc->unit, larg);
			break;
		}
		break;
#endif 							  
	case DAGIOCMONITOR:
		error = -EFAULT;
		break;

	case DAGIOCINFO:
		if (sc->device_id)
		{		
			info.device_code = sc->device_id;
			info.brd_rev = sc->brd_rev;
		}
		else
		{
			info.device_code = PCI_DEVICE_ID_VDAG;
			info.brd_rev  = 0; 
		}

		info.id = sc->unit;
		
		if (sc->page_list && sc->page_list[0])
			info.phy_addr = ((unsigned long)page_to_phys(sc->page_list[0])) & 0xffffffff;
		else
			info.phy_addr = 0xDEADBEEF;
		info.buf_size = sc->membuf_size;;
		info.iom_size = sc->drb_regs_size;

		strncpy(info.bus_id, "vDAG", DAG_ID_SIZE);
		if(copy_to_user((void*)arg, &info, sizeof(daginf_t)))
			error = -EFAULT;
		break;

	case DAGIOCSTREAMSTAT:
		__get_user(larg, (int32_t*)arg);
		stream_num = (larg >>16) & 0xffff;
		if(stream_num >= DAG_STREAM_MAX) {
			error = -EINVAL;
			break;
		}
		DAG_DEBUG("vdag: ioctl: stream_num %d, stat = 0x%08X\n", stream_num, sc->stream_stat[stream_num]);

		if(copy_to_user((void*)arg, &sc->stream_stat[stream_num], sizeof(sc->stream_stat[0])))
			error = -EFAULT;
		sc->stream_stat[stream_num] &= ~(uint32_t)(0x1);
		break;
	case DAGIOCLOCK:
		__get_user(larg, (int*)arg);
        	mode = (larg >> 8) & 0x1;
		op = larg & DAG_LOCK_OP;

		/* Locks are hierarchial
		 * ONLY do the highest precedence op requested
		 */

		if (larg & DAG_LOCK_CARD) {
			error = vdag_card_lock(fp, op);

		} else if (larg & DAG_LOCK_MODE) {
			error = vdag_mode_lock(fp, mode, op);

		} else {
			stream_num = (larg >>16) & 0xffff;
			error = vdag_stream_lock(fp, stream_num, mode, op);
		}

		break;

	case DAGIOCDUCK:
		error = 0;
		break;
#if 0 
		//TODO: If we want complete duck emulation 
		if(copy_from_user(&duckinf, (void*)arg, sizeof(duckinf_t))) {
			error = -EFAULT;
			break;
		}
		error = duck_ioctl(sc->duck, &duckinf);
		if(copy_to_user((void*)arg, &duckinf, sizeof(duckinf_t)))
			error = -EFAULT;
		break;
#endif 		

	case DAGIOCALLOCMEM:
		/* We must protect against multiple use of file descriptor, it is not the 
		best now as nobody guarantee us that already opened descriptor has not pages 
		in use at this moment. To fix this properly whole approach needs to be 
		reviewed */
		if (copy_from_user(&mem_user,(void*)arg, sizeof(user_mem_t))) {
				error = -EFAULT;
				break;
		}
		error = vdag_membuf_alloc(mem_user.membuf_size, mem_user.node, sc);
		break;

	case DAGIOCFREEMEM:
		vdag_membuf_free(sc);
		error = 0;
		break;

	case DAGIOCMEMINFO:
		vdag_get_mem_info(sc, &mem_info);
		if(copy_to_user((void*)arg, &mem_info, sizeof(mem_info)))
			error = -EFAULT;
		break;

	case DAGIOCDEVINFO:
		__get_user(larg, (int*)arg);
		dev_code = larg & 0XFFFF;
		brd_rev = (larg >> 16) & 0xF;
		
		DAG_DEBUG("vdag: device_code:%x brd_rev:%x\n",dev_code, brd_rev);
		/* set the device id and board revision */
		sc->device_id = dev_code;
		sc->brd_rev = brd_rev;
		
		break;

	default:
		error = -ENOTTY;
		break;
	}

	//restore the original task state 
	set_current_state(tmp_state);
	
	up(&sc->lock_sem);
	return error;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)
static int vdag_flush(struct file *fp, fl_owner_t id)
#else
static int vdag_flush(struct file *fp)
#endif
{

	vdag_softstate_t	*sc = fp->private_data;	
	int index;
	
	down(&sc->lock_sem);

	vdag_card_lock(fp, DAG_LOCK_OP_RELEASE);

	vdag_mode_lock(fp, DAG_NORMAL_MODE, DAG_LOCK_OP_RELEASE);
	vdag_mode_lock(fp, DAG_REVERSE_MODE, DAG_LOCK_OP_RELEASE);

	/* try unlocking all streams in NORMAL and REVERSE modes */
	for (index = 0; index < DAG_STREAM_MAX; index++)
	{
		vdag_stream_lock(fp, index, DAG_NORMAL_MODE, DAG_LOCK_OP_RELEASE);
		vdag_stream_lock(fp, index, DAG_REVERSE_MODE, DAG_LOCK_OP_RELEASE);
	}
	up(&sc->lock_sem);
	return 0;
}

static int
vdag_release(struct inode *inode, struct file *file)
{
	DAG_DEBUG(" called \n");

	return 0;
}


static void vdag_timer_handler(unsigned long unused)
{
	vdag_monitor(NULL);

	if(!timer_shutdown)
	{
		vdag_timer.expires = jiffies + timer_delay;
		add_timer(&vdag_timer);
    	}
    	return;
}

/*
 *
 *
 *
 *
 *
 */
static void * vdag_monitor(void * unused)
{
	vdag_softstate_t	*sc;
	int idx,  card_idx, stream_num, rx_stream_cnt, tx_stream_cnt;
	uint32_t tmp32;

	for(card_idx = 0; card_idx < VDAG_MAX_BOARDS; card_idx++)
	{
		sc = &vdag_array[card_idx];
		/* get board semaphore, having xxx_trylock is essential here 
		 * since timer handler is executed in software interrupt context,
		 * no sleep is allowed 
		 */
		if(!down_trylock(&sc->lock_sem))
		{
			/* check if drb is configured and card lock is not held */
			if(sc->drb_regs && (!sc->card_lock.pid))
	{

				tmp32 =  *(uint32_t *)(sc->drb_regs + 0x200c);
				rx_stream_cnt = tmp32 & 0x7FF;
				tx_stream_cnt = (tmp32 >> 16) & 0x7FF ;

				/* check all receive streams */
				for(idx = 0; idx < rx_stream_cnt; idx++)
        	{
					stream_num = 2*idx;
					vdag_stream_start(sc, stream_num);
				}

				/* check all transmit streams */
				for(idx = 0; idx < tx_stream_cnt; idx++)
			{
					stream_num = 2*idx + 1;
					vdag_stream_start(sc, stream_num);
			}

        	}

			up(&sc->lock_sem);
		}
    	}
	return NULL;
}


/*
 *
 * MUST hold lock_sem before calling
 *
*/
static int vdag_stream_start(vdag_softstate_t *sc, int stream_num)
{
	dagpbm_stream_MkIII_t *pbm_regs;
	/*
	 * process if
	 * 1. normal mode lock is help or the stream is locked in normal mode 
	 * 2. reverse side is not connected.
	 * 3. reverse mode lock is not held
	 */
	if((sc->mode_lock[0].pid || sc->stream_lock[stream_num][0].pid) &&
	   (!sc->stream_lock[stream_num][1].pid) &&
	   (!sc->mode_lock[1].pid))
	{
		/* check mem_size != 0 */
		pbm_regs = (dagpbm_stream_MkIII_t *)(sc->drb_regs + 0x2000 + (stream_num + 1) * 0x40);
		if(pbm_regs->mem_size)
		{
			if(pbm_regs->status & DAGPBM_REQPEND)
			{
				pbm_regs->status &= ~(DAGPBM_REQPEND);
			}
			if(pbm_regs->status & DAGPBM_SYNCL2R)
			{
				/* write record pointer here */
				if(stream_num & 1)
				{
					pbm_regs->record_ptr = pbm_regs->mem_addr + SAFETY_WINDOW;
				}
				else
				{
					pbm_regs->record_ptr = pbm_regs->mem_addr;
				}

				DAG_DEBUG("vdag: start: stream_num %d, stat = 0x%08X\n", stream_num, sc->stream_stat[stream_num]);
				/* clear DAGPBM_SYNCL2R */
				pbm_regs->status &= ~(DAGPBM_SYNCL2R | DAGPBM_PAUSED);
				sc->stream_stat[stream_num] |= (uint32_t)0x1;
				DAG_DEBUG("vdag: start: stream_num %d, stat = 0x%08X\n", stream_num, sc->stream_stat[stream_num]);
			}
		}

    	}
	return 0;
}

struct file_operations vdag_fops = {
	/* V dag DRB access entry */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	owner:  THIS_MODULE,
#endif
	read:	vdag_iom_read,
	write:	vdag_iom_write,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)
	unlocked_ioctl:	vdag_iom_unlocked_ioctl,
#else
	ioctl:	vdag_iom_ioctl,
#endif
	mmap:	vdag_mmap,
	open:	vdag_open,
	flush: 	vdag_flush,
	release: vdag_release
};




struct file_operations vdag_iom_fops = {
	/* V dag DRB access entry */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	owner:  THIS_MODULE,
#endif
	read:	vdag_iom_read,
	write:	vdag_iom_write,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)
	unlocked_ioctl:	vdag_iom_unlocked_ioctl,
#else
	ioctl:	vdag_iom_ioctl,
#endif
	mmap:	vdag_iom_mmap,
	open:	vdag_open,
	//release: vdag_release
};



MODULE_DESCRIPTION("vdag kernel module by Endace");
MODULE_AUTHOR("DAG Team <support@endace.com>");
MODULE_LICENSE("Proprietary Endace Measurement Systems Ltd");
MODULE_VERSION(PACKAGE_VERSION);

/* Service function to parse extended integer values with KB or MB at
 * the end*/
static void vdag_parse_int_specs(char * param, unsigned int* retval)
{
    char	*more;

    if(param[0] == '\0') 
        return;

    *retval = simple_strtoul(param, &more, 0);
    switch(*more) {
        case '\0':
            break;
        case 'M':
        case 'm':
            *retval *= 1024 * 1024;
            break;
        case 'K':
        case 'k':
            *retval *= 1024;
            break;
        default:
            DAG_INFO("vdag: unrecognized character '%c' specification\n",
                    *more);
            break;			
    }
    if((*more != '\0') && (*++more != '\0'))
        DAG_INFO("vdag: extra character '%c' after param=<N> specification\n", *more);

    if(*retval & (PAGE_SIZE - 1)) {
        *retval = VDAG_DEFAULT_MEMSIZE;
        DAG_INFO("vdag: given value is not page aligned! The default value of %u bytes is taken \n", VDAG_DEFAULT_MEMSIZE);
    }

    return;
}

/* Main function to parse all input params */
static void vdag_parse_params(void)
{
    /* Parse dsize parameters ***/
    vdag_parse_int_specs(dsize, &vdag_mem_size);

}

static int vdag_init_module(void)
{
	int	err, i;

	/* Due we are using for all vdag cards a single init module 
	we can not allocate the private data here 
	we have to allocate it for all vdags or do it at first open 
	*/

	err = 0;
	DAG_INFO("vdag: Version %s\n", kDagReleaseVersion);

	vdag_parse_params(); /*no param to the function.support only size which is stored in global var*/

	err = register_chrdev(vdag_iom_major, "vdag", &vdag_iom_fops);
  	if (err < 0) {
	DAG_ERROR("vdag: IOM cannot obtain major number %d\n", vdag_iom_major);
    		return err;
	}

	vdag_iom_major = err;
	DAG_DEBUG("vdag: IOM started with major number %d \n", vdag_iom_major);

	err = register_chrdev(vdag_major, "vdag", &vdag_fops);
	if (err < 0) {
	DAG_ERROR("vdag: cannot obtain major number %d\n", vdag_major);
		return err;
	};

	vdag_major = err;
	DAG_DEBUG("vdag: DAG started with major number %d \n", vdag_major);

	for(i = 0 ; i < VDAG_MAX_BOARDS; i++ )
	{
		/*
		 * init the semaphore for every device in vdag_array[].
		 * this vdag_array[i].lock_sem can be used for any function after loading the driver.
		 * it is important to do it here in init function before any process can start
		 * using devices.
		 */
		sema_init(&vdag_array[i].lock_sem, 1);
	}

	timer_shutdown = 0;
	timer_delay = HZ/100;
	vdag_timer.expires = jiffies + timer_delay;
	add_timer(&vdag_timer);

	return 0;
}


static void vdag_exit_module(void)
{
	void *pageptr;
	struct page *page;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
	int err;
#endif
	int i ;

    	timer_shutdown = 1;
    	del_timer_sync(&vdag_timer);
   
	//TODO : Clean up code 
	for(i =0 ; i < VDAG_MAX_BOARDS; i++ )
	{
		//Release the DRB space 
		if ( vdag_array[i].drb_regs != NULL ) 
		{

			pageptr = vdag_array[i].drb_regs;
			page = vmalloc_to_page(pageptr);

			vfree(vdag_array[i].drb_regs);
			vdag_array[i].drb_regs = NULL;
		};
		vdag_membuf_free(&vdag_array[i]);
		vdag_array[i].unit = 0;
	}
	
	DAG_DEBUG("vdag: vdag DAG unloading major:%d\n", vdag_major);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23)
	unregister_chrdev(vdag_major, "vdag");
#else
	err = unregister_chrdev(vdag_major, "vdag");
	if (err < 0) {
		DAG_ERROR("unregister_chrdev error : major number %d ret: %d\n", vdag_major,err);
	}
#endif
	
	DAG_DEBUG("vdag: vdag IOM unloading major:%d\n", vdag_iom_major);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23)
	unregister_chrdev(vdag_iom_major, "vdagiom");
#else
	err = unregister_chrdev(vdag_iom_major, "vdagiom");
  	if (err < 0) {
    		DAG_ERROR("unregister_chrdev error : major number %d ret: %d\n", vdag_iom_major,err);
	}
#endif
	
}

module_init(vdag_init_module);
module_exit(vdag_exit_module);
