/** @file ioctl.c
*
* @author Sylvain Nahas
*
* This module implements the apci3120_do_ioctl function
*/

/** @par LICENCE
* @verbatim
Copyright (C) 2009  ADDI-DATA GmbH for the source code of this module.

ADDI-DATA GmbH
Airpark Business Center
Airport Boulevard B210
77836 Rheinmuenster
Germany
Tel: +49(0)7229/1847-0
Fax: +49(0)7229/1847-200
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 <linux/version.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
#include <linux/config.h>
#else
	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
		#include <linux/autoconf.h>
	#else
		#include <generated/autoconf.h>
	#endif
#endif

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/pci.h>
#include <asm/io.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,4,0)
	#include <asm/system.h>
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0)
	#include <asm/uaccess.h>
#else
	#include <linux/uaccess.h>
#endif
#include <asm/bitops.h>
#include <linux/sched.h>

#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/delay.h>

#include "apci3120-private.h"
#include "vtable.h"

EXPORT_NO_SYMBOLS;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,27)
#define __user
#endif

//------------------------------------------------------------------------------
/** dummy command to be called when a ioctl command is incorrect */
static int apci3120_do_dummy(struct pci_dev * pdev, unsigned int cmd, unsigned long arg)
{
	printk (KERN_WARNING "%s: %d: invalid ioctl\n",__DRIVER_NAME,_IOC_NR(cmd));
	return -EINVAL;
}
//------------------------------------------------------------------------------

/** add new ioctl handlers here */
void apci3120_init_vtable(vtable_t vtable)
{
	apci3120_dummyze_vtable(vtable, apci3120_do_dummy);


	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_CheckAndGetPCISlotNumber,do_CMD_APCI3120_CheckAndGetPCISlotNumber);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_GetHardwareInformation,do_CMD_APCI3120_GetHardwareInformation);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_SetBoardInformation,do_CMD_APCI3120_SetBoardInformation);

	//------------------------------------------------------------------------------

	/* Analog input */
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_Read1AnalogInput,do_CMD_APCI3120_Read1AnalogInput);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_ReadMoreAnalogInputs,do_CMD_APCI3120_ReadMoreAnalogInputs);

	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_InitReadMoreAnalogInput,do_CMD_APCI3120_InitReadMoreAnalogInput);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_StartReadMoreAnalogInput,do_CMD_APCI3120_StartReadMoreAnalogInput);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_TestEndOfReadMoreConversion,do_CMD_APCI3120_TestEndOfReadMoreConversion);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_TestReadMoreAnalogInput,do_CMD_APCI3120_TestReadMoreAnalogInput);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_ReadMoreValues,do_CMD_APCI3120_ReadMoreValues);

	//------------------------------------------------------------------------------

	/* Simple analog output */
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_Write1AnalogOutput,do_CMD_APCI3120_Write1AnalogOutput);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_WriteMoreAnalogOutput,do_CMD_APCI3120_WriteMoreAnalogOutput);

	//------------------------------------------------------------------------------

	/* Digital I/O */
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_Read4DigitalInput,do_CMD_APCI3120_Read4DigitalInput);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_SetOutputMemoryOn,do_CMD_APCI3120_SetOutputMemoryOn);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_SetOutputMemoryOff,do_CMD_APCI3120_SetOutputMemoryOff);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_Set4DigitalOutputOn,do_CMD_APCI3120_Set4DigitalOutputOn);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_Set4DigitalOutputOff,do_CMD_APCI3120_Set4DigitalOutputOff);

	//------------------------------------------------------------------------------

	/* Sequence */
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_InitAnalogInputSequenceAcquisition,do_CMD_APCI3120_InitAnalogInputSequenceAcquisition);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_StartAnalogInputSequenceAcquisition,do_CMD_APCI3120_StartAnalogInputSequenceAcquisition);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_StopSequenceAcquisition,do_CMD_APCI3120_StopSequenceAcquisition);

	//------------------------------------------------------------------------------

	/* Interruption */
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_SetBoardInterruptRoutine,do_CMD_APCI3120_SetBoardInterruptRoutine);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_APCI3120_ResetBoardInterruptRoutine,do_CMD_APCI3120_ResetBoardInterruptRoutine);

	/*
	 * Event
	 */
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_apci3120_EventWait,do_CMD_apci3120_EventWait);
	__APCI_3120_DECLARE_IOCTL_HANDLER(vtable,CMD_apci3120_EventEmpty,do_CMD_apci3120_EventEmpty);
}

//------------------------------------------------------------------------------
int apci3120_do_ioctl(struct pci_dev * pdev,unsigned int cmd,unsigned long arg)
{

	/* boundaries check
	 *
	 * VTABLE_ELEMENT_NB(vtable_t) = __APCI3120_UPPER_IOCTL_CMD +1
	 * maximum index value = __APCI3120_UPPER_IOCTL_CMD = VTABLE_ELEMENT_NB(vtable_t) -1
	 *
	 * the idea here is to favorize compilation-time
	 *
	 * */

	if (_IOC_NR(cmd) > (VTABLE_ELEMENT_NB(vtable_t)-1) )
		return apci3120_do_dummy(pdev,cmd,arg);

	/* call actual ioctl handler - should be safe now */
	return (apci3120_vtable[_IOC_NR(cmd)]) (pdev, cmd, arg);
}
//------------------------------------------------------------------------------
int do_CMD_APCI3120_CheckAndGetPCISlotNumber(struct pci_dev * pdev, unsigned int cmd, unsigned long arg)
{
	int retval = 0; /* return value of this call is the number of boards */

	/* this command returns an area of size CONFIG_APCI3120_MAX_BOARD_NBR+1 filled with the PCI_SLOT() value and as last field the major number associated to the device - this is deprecated and should not be used anymore.
	The call returns 0 when memory is too low or the number of boards  */

	char * b_SlotArray = kmalloc(CONFIG_APCI3120_MAX_BOARD_NBR+1 * sizeof(char),GFP_KERNEL);
	if (!b_SlotArray)
	{
		/* bad param. Let output some information for debug */
		printk("%s: CMD_APCI3120_CheckAndGetPCISlotNumber: -EINVAL\n",__DRIVER_NAME);
		return 0;
	}

	memset(b_SlotArray,0,CONFIG_APCI3120_MAX_BOARD_NBR+1 );

	printk("%s: the use of CMD_APCI3120_CheckAndGetPCISlotNumber is deprecated\n",__DRIVER_NAME);

	/* record the PCI_SLOT for each device from 0 to CONFIG_APCI3120_MAX_BOARD_NBR  */
	{
		int i;
		for(i = 0; ( ( i < atomic_read(&apci3120_count) ) || ( i< CONFIG_APCI3120_MAX_BOARD_NBR ) ); i++)
		{

			struct pci_dev * dev =  apci3120_lookup_board_by_index(i);

			if (!dev) break;

			b_SlotArray[i] = PCI_SLOT(dev->devfn);

		}
	}
	b_SlotArray[CONFIG_APCI3120_MAX_BOARD_NBR] = apci3120_majornumber;

	if ( copy_to_user ( (unsigned char __user *)arg, b_SlotArray, sizeof (uint8_t[CONFIG_APCI3120_MAX_BOARD_NBR+1])) )
	{
		/* bad address. Let output some information for debug */
		printk("%s: CMD_APCI3120_CheckAndGetPCISlotNumber: -EFAULT\n",__DRIVER_NAME);
		kfree(b_SlotArray);
		return 0;
	}
	/* return the smaller value between CONFIG_APCI3120_MAX_BOARD_NBR and number of board - Note: apci3120_count is assumed to to be always consistent with the system PCI devices list */
	retval = ( atomic_read(&apci3120_count) < CONFIG_APCI3120_MAX_BOARD_NBR ? atomic_read(&apci3120_count) : CONFIG_APCI3120_MAX_BOARD_NBR );

	kfree(b_SlotArray);

	return retval;

}



//------------------------------------------------------------------------------
/** Returns the informations of base address, IRQ to the user.
* @deprecated: use-space doesn't need it and kernel-space has better means to get it.
*  */
int do_CMD_APCI3120_GetHardwareInformation(struct pci_dev * pdev, unsigned int cmd, unsigned long arg)
{
	str_BaseInformations s_BaseInformations;
	s_BaseInformations.ui_BaseAddress[0] = GET_BAR0(pdev);
	s_BaseInformations.ui_BaseAddress[1] = GET_BAR1(pdev);
	s_BaseInformations.ui_BaseAddress[2] = GET_BAR2(pdev);
	s_BaseInformations.ui_BaseAddress[3] = 0;
	s_BaseInformations.ui_BaseAddress[4] = 0;
	s_BaseInformations.b_Interrupt = pdev->irq;
	s_BaseInformations.b_SlotNumber = PCI_SLOT(pdev->devfn);

	if ( copy_to_user ((str_BaseInformations __user *)arg,&s_BaseInformations, sizeof(s_BaseInformations)) )
		return -EFAULT;

	return 0;
}

//------------------------------------------------------------------------------
int do_CMD_APCI3120_SetBoardInformation(struct pci_dev * pdev, unsigned int cmd, unsigned long arg)
{
	/* Test the number of channels */
	if ((arg < 4) || (arg > 16))
		return 3;

	APCI3120_PRIVDATA(pdev)->nrAnaIn = (uint8_t)arg;

	return 0;
}
