/** @file fs.c
*
* @author Sylvain Nahas
*
* This module provides FS related function implementation.
*/

/** @par LICENCE
* @verbatim
 Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code and the documentation.
        
        ADDI-DATA GmbH
        Dieselstrasse 3 
        D-77833 Ottersweier
        Tel: +19(0)7223/9493-0
        Fax: +49(0)7223/9493-92
        http://www.addi-data-com
        info@addi-data.com
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details. 
        
    You shoud find the complete GPL in the COPYING file accompanying 
    this source code. 
* @endverbatim
*/


#include "apci3600-private.h"


#ifdef FS_DEBUG
#define FS_DPRINTK(fmt, args...)	printk("%s: " fmt, __FUNCTION__ , ## args)
#define FS_MARK	printk("%s:%i\n", __FUNCTION__, __LINE__)
#else
#define FS_DPRINTK(fmt, args...)
#define FS_MARK
#endif	


EXPORT_NO_SYMBOLS;

//------------------------------------------------------------------------------
/** open() function of the module for the APCI-3600, with lookup though global OS PCI list
* this is for files unmanaged by the driver itself and use the minor device number to identify the board.
*
* When opening, the pci_dev associated to the minor number is looked up
* and associated with the file structure. It avoid further lookup when calling ioctl()
* 
*
*/
int apci3600_fs_open (struct inode *inode, struct file *filp)
{
	struct pci_dev * pdev = NULL;
	if ( apci3600_INDEX_NOT_VALID(&apci3600_count, MINOR(inode->i_rdev) ) )
	{
		return -ENODEV;
	}

	FS_DPRINTK("opened file with minor: %i board(%i)\n", MINOR(inode->i_rdev), APCI3600_FROM_MINOR_GET_BOARD_INDEX(MINOR(inode->i_rdev)));
	pdev = apci3600_lookup_board_by_index( APCI3600_FROM_MINOR_GET_BOARD_INDEX(MINOR(inode->i_rdev)) );
	
	if(!pdev)
		return -ENODEV;

	filp->private_data = pdev;

	MOD_INC_USE_COUNT;
	apci3600_inc_process_count(pdev);

	return 0;
}
//------------------------------------------------------------------------------
/** open() function of the module for the APCI-3600, with lookup though global OS PCI list
* this is for files unmanaged by the driver itself and use the minor device number to identify the board.
*
* When opening, the pci_dev associated to the minor number is looked up
* and associated with the file structure. It avoid further lookup when calling ioctl()
* 
*
*/
ssize_t apci3600_fs_read (struct file *filp, char __user * pdata, size_t count, loff_t* off)
{
	struct pci_dev * pdev = filp->private_data;  
	
	if (!pdev) // private data is initialised to NULL
	{
		printk(KERN_CRIT "%s: %s: board data should be affected but is not (did you call open() before ioctl() ?) \n",__APCI3600_DRIVER_NAME, __FUNCTION__);
		return -EBADFD;
	}

	/* read available data */
	{
		int ret;
		unsigned long irqstate;
		APCI3600_LOCK(pdev,&irqstate);
		{
			ret = apci3600_do_read_available_data(pdev, filp, pdata, count, off);
		}
		APCI3600_UNLOCK(pdev,irqstate);
		return ret;
	}
}
//------------------------------------------------------------------------------
/** release() function of the module for the APCI-3600, with lookup though global OS PCI list
* this is for files unmanaged by the driver itself and use the minor device number to identify the board
*/
int apci3600_fs_release (struct inode *inode,struct file *filp)
{
	struct pci_dev * pdev = filp->private_data;

	if ( apci3600_INDEX_NOT_VALID(&apci3600_count, MINOR(inode->i_rdev) ) )
	{
		return -ENODEV;
	}

	MOD_DEC_USE_COUNT;
	apci3600_dec_process_count(pdev);
	
	/* if there is no more references to this module, we stop the hardware */ 
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	if ( !(apci3600_get_process_count(pdev)) )
#else
	if ( (! module_refcount(THIS_MODULE)) && !(apci3600_get_process_count(pdev)) )
#endif
	{
		unsigned long irqstate;
		APCI3600_LOCK(pdev,&irqstate);
		{
			i_PCI3600_Intern_CloseHardware (pdev);
		}
		APCI3600_UNLOCK(pdev,irqstate);
	}
	return 0;
}
//------------------------------------------------------------------------------
/** ioctl() function of the module for the APCI-3600, with lookup though global OS PCI list
* this is for files unmanaged by the driver itself and use the minor device number to identify the board
*/
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36)
int apci3600_fs_ioctl (struct inode *inode,
							struct file *filp,
							unsigned int cmd,
							unsigned long arg)
#else
long apci3600_fs_ioctl (struct file *filp,
							unsigned int cmd,
							unsigned long arg)
#endif
{
	int ret = 0;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
	#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,19,0)
		struct inode *inode = filp->f_path.dentry->d_inode;
	#else
		struct inode *inode = filp->f_dentry->d_inode;
	#endif
#endif

	int minor = MINOR(inode->i_rdev);
	
	FS_MARK;	
	
	if ( apci3600_INDEX_NOT_VALID(&apci3600_count, minor ) )
		return -ENODEV;

	if (!filp->private_data) // private data is initialised to NULL
	{
		printk(KERN_CRIT "%s: %s: board data should be affected but is not (did you call open() before ioctl() ?) \n",__APCI3600_DRIVER_NAME, __FUNCTION__);
		return -EBADFD;
	}

	{
		struct pci_dev * pdev =  filp->private_data;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)
        lock_kernel();
#endif
		switch( APCI3600_COMPUTE_SUBSYS_INDEX(minor) )
		{
			case APCI3600_DEV_MINOR_BASE:
				FS_MARK;
				ret = apci3600_do_ioctl_base(pdev, cmd, arg);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)
                unlock_kernel();
#endif
				return ret;
			case APCI3600_DEV_MINOR_ANALOG_INPUT_0:
				FS_MARK;
				ret = apci3600_do_ioctl_analog_input( pdev, cmd,arg, 0);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)
				unlock_kernel();
#endif
				return ret;
			case APCI3600_DEV_MINOR_ANALOG_INPUT_1:
				FS_MARK;
				ret = apci3600_do_ioctl_analog_input( pdev, cmd,arg, 1);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)
				unlock_kernel();
#endif
				return ret;
			case APCI3600_DEV_MINOR_ANALOG_INPUT_2:
				FS_MARK;
				ret = apci3600_do_ioctl_analog_input( pdev, cmd,arg, 2);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)
				unlock_kernel();
#endif
				return ret;
			case APCI3600_DEV_MINOR_ANALOG_INPUT_3:
				FS_MARK;
				ret = apci3600_do_ioctl_analog_input( pdev, cmd,arg, 3);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)
				unlock_kernel();
#endif
				return ret;
			case APCI3600_DEV_MINOR_EXTERNAL_TRIGGER:
				FS_MARK;
				ret = apci3600_do_ioctl_external_trigger( pdev, cmd,arg);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)
				unlock_kernel();
#endif
				return ret;
			case APCI3600_DEV_MINOR_DIGITAL_OUTPUT:
				FS_MARK;
				ret = apci3600_do_ioctl_digital_output(pdev, cmd, arg);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)
				unlock_kernel();
#endif
				return ret;
			case APCI3600_DEV_MINOR_DIGITAL_INPUT:
				FS_MARK;
				ret = apci3600_do_ioctl_digital_input(pdev, cmd, arg);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)
				unlock_kernel();
#endif
				return ret;
			case APCI3600_DEV_MINOR_ANALOG_OUTPUT_0:
			case APCI3600_DEV_MINOR_ANALOG_OUTPUT_1:

			default:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)
				unlock_kernel();
#endif
				return -ENOTSUPP;
		}
	}
}
////------------------------------------------------------------------------------
///** mmap() function of the module for the APCI-3600. We map only the ring buffer
//*/
//
//int apci3600_fs_mmap (struct file *file, struct vm_area_struct *vma)
//{
//	//vma: structure send by mmap call in user mode. It contains the parameters of the memory we want to mmap between the user and kernel mode
//
//	//vm_pgoff: offset in pages
//	//unsigned long off = vma -> vm_pgoff << PAGE_SHIFT;
//	/*size the user want to share with the kernel*/
//	unsigned long vma_size = vma-> vm_end - vma->vm_start;
//		
//	//address of the Ring buffer of the MSX-Box
//	unsigned long physical_RingBuffer_addr = 0;
//	//size of this buffer
//	unsigned long RingBuffer_size = 0;
//	int ret;
//
//	struct pci_dev * pdev = NULL;
//
//// 	unsigned long offset = VMA_OFFSET(vma);
//
//	if (!file->private_data) // private data is initialised to NULL
//	{
//		printk(KERN_CRIT "%s: %s: board data should be affected but is not (did you call open() before ioctl() ?) \n",__APCI3600_DRIVER_NAME, __FUNCTION__);
//		return -EBADFD;
//	}
//	FS_DPRINTK("The mapping begins\nvma->pgoff: %08lX\n",vma -> vm_pgoff);
//
//
//// 	if (offset >= __pa (high_memory) || (file -> f_flags & O_SYNC))
//// 		vma -> vm_flags |= VM_IO;
//// 	vma -> vm_flags |= VM_RESERVED;
//
//	pdev = (struct pci_dev*) file->private_data;
//
//	/*According to CG, we must hide the Descriptor to the end user (user mode program)*/
//	/*here is the ring buffer address, i.e. the physical address of tje pci_alloc_consistent buffer plus the descriptor and the alignement*/
//
//	physical_RingBuffer_addr =	APCI3600_PRIVDATA(pdev)->
//					s_PCI3600_BoardInformation.
//					s_AnaInpChronomPCDMABufferInfos.
//					dw_PhysicalAddress;
//
//	RingBuffer_size = 	APCI3600_PRIVDATA(pdev)->
//					s_PCI3600_BoardInformation.
//					s_AnaInpChronomPCDMABufferInfos.
//					dw_BufferSize;
//	/*we check if the size off the buffer to map is greater than the mmap buffer*/
//	if (vma_size > RingBuffer_size){
//		printk(KERN_CRIT "the vma size is to big (0x%lX>0x%lX)\n",vma_size,RingBuffer_size);
//		return -EINVAL;
//
//	}
//	FS_DPRINTK("mmap:\tphysical address to map (physical_RingBuffer_addr):\t0x%08lX\n",physical_RingBuffer_addr);
//
//	/*About virt_to_phys(): It is only valid to use this function on addresses directly mapped or allocated via kmalloc.*/
//	/*we want to mmap() what the user want, without the descriptor
//	io_remap_page_range seems to be the same as remap_page_range, Rodolphe used remap_page_range.
//	*/
//	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)  
// 		ret= /*io_*/remap_page_range (vma->vm_start, physical_RingBuffer_addr, vma->vm_end-vma->vm_start, /*PAGE_SHARED*/vma-> vm_page_prot);   
//	#else
//		ret= /*io_*/remap_pfn_range (vma,vma->vm_start, physical_RingBuffer_addr, vma->vm_end-vma->vm_start, /*PAGE_SHARED*/vma-> vm_page_prot);   
//	#endif
//	
//
//	/*test of the alignement*/
//
//	if (physical_RingBuffer_addr& (PAGE_SIZE-1))
//		FS_DPRINTK("\tThis address seems unaligned %lX (%d)\n",physical_RingBuffer_addr,get_order(physical_RingBuffer_addr));
//
//
//	FS_DPRINTK("mmap:\tphysical address of the ring buffer (ie: __pa(RingBufferAddress)):\t0x%08lX\n",__pa(APCI3600_PRIVDATA(pdev)->
// 					s_PCI3600_BoardInformation.
// 					s_AnaInpChronomPCDMABufferInfos.
// 					pdw_DMABuffer[0]));
//	FS_DPRINTK("mmap:\tPCI3600_DESCRIPTOR_MEM_SIZE:\t0x%08X\n",PCI3600_DESCRIPTOR_MEM_SIZE);
//
//
//
//	FS_DPRINTK("\tsize we want to map :\t0x%08lX\n",vma_size);
//	FS_DPRINTK("\tsize of the RingBuffer buffer (must be greater than the vma size) :\t0x%08lX\n",RingBuffer_size);
//	FS_DPRINTK("vma : vma->vm_start :\t0x%08lX\n",vma->vm_start);
//	FS_DPRINTK("vma : vma->vm_end :\t0x%08lX\n",vma->vm_end);
//	FS_DPRINTK("vma : vma->vm_start (phys) :\t0x%08lX\n",__pa(vma->vm_start));
//	FS_DPRINTK("vma : vma->vm_end (phys):\t0x%08lX\n",__pa(vma->vm_end));
//	
//	FS_DPRINTK("page size :\t0x%lX\n",PAGE_SIZE);
//	
//		
//
//	if (ret !=0){
//		printk (KERN_CRIT "failed while mapping\n");
//	  	return -EAGAIN;
//	}
//	FS_DPRINTK("Successful mapping\n");
//	return (0);
//
//}

//------------------------------------------------------------------------------
//Begin 8/11/2005 CC
// Adds a Signal listener to the list (USR program who has requested to be notified by
// this module with signals - SIGIO) - This allows signal communication. See dev_fops
int apci3600_fs_fasync (int fd, struct file *filp, int mode)
{
	struct pci_dev * pdev = (struct pci_dev*) filp->private_data; 
	return fasync_helper (fd, filp, mode, & (APCI3600_PRIVDATA(pdev)->fa) );

}
//End 19/04/2006 FB
