/** @file ioctl.c
*
* @author Sylvain Nahas, Julien Krauth
*
* This module implements the apcie1532_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 Rheinm�nster
        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 <apcie1532-kapi.h>
#include "apcie1532-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 apcie1532_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 apcie1532_init_vtable(vtable_t vtable)
{
	apcie1532_dummyze_vtable(vtable, apcie1532_do_dummy);


	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_CheckAndGetPCISlotNumber,do_CMD_APCIE1532_CheckAndGetPCISlotNumber);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_GetHardwareInformation,do_CMD_APCIE1532_GetHardwareInformation);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_SetBoardInformation,do_CMD_APCIE1532_SetBoardInformation);

	/* Digital inputs functions (dig_inputs.c) */
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_Read16DigitalInputs,do_CMD_APCIE1532_Read16DigitalInputs);

	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_InitAndStartDigitalInputInterrupt,do_CMD_APCIE1532_InitAndStartDigitalInputInterrupt);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_ClearAndStopDigitalInputInterrupt,do_CMD_APCIE1532_ClearAndStopDigitalInputInterrupt);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_GetDigitalInputInterruptConfig,do_CMD_APCIE1532_GetDigitalInputInterruptConfig);

	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_EventEmpty,do_CMD_APCIE1532_EventEmpty);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_EventWait,do_CMD_APCIE1532_EventWait);

	/* Digital outputs functions (dig_outputs.c) */
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_Set16DigitalOutputsOn,do_CMD_APCIE1532_Set16DigitalOutputsOn);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_Set16DigitalOutputsOff,do_CMD_APCIE1532_Set16DigitalOutputsOff);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_SetDigitalOutputMemoryOn,do_CMD_APCIE1532_SetDigitalOutputMemoryOn);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_SetDigitalOutputMemoryOff,do_CMD_APCIE1532_SetDigitalOutputMemoryOff);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_Get16DigitalOutputStatus,do_CMD_APCIE1532_Get16DigitalOutputStatus);

	/* Counter (counter.c) */
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_CounterInitAndStart, do_CMD_APCIE1532_CounterInitAndStart);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_ReadCounterValue, do_CMD_APCIE1532_ReadCounterValue);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_CounterStop, do_CMD_APCIE1532_CounterStop);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_CounterGetStatus, do_CMD_APCIE1532_CounterGetStatus);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_CounterGetConfigRegister, do_CMD_APCIE1532_CounterGetConfigRegister);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_CounterReset, do_CMD_APCIE1532_CounterReset);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_CounterTrigger, do_CMD_APCIE1532_CounterTrigger);

	/* TimerWatchdog (timer_watchdog.c) */
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_TimerWatchdogInitAndStart, do_CMD_APCIE1532_TimerWatchdogInitAndStart);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_ReadTimerWatchdogValue, do_CMD_APCIE1532_ReadTimerWatchdogValue);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_TimerWatchdogStop, do_CMD_APCIE1532_TimerWatchdogStop);
	__APCIE_1532_DECLARE_IOCTL_HANDLER(vtable,CMD_APCIE1532_TimerWatchdogTrigger, do_CMD_APCIE1532_TimerWatchdogTrigger);
}

//------------------------------------------------------------------------------
/** IOCTL calls in user space.
 */
int apcie1532_do_ioctl(struct pci_dev * pdev,unsigned int cmd,unsigned long arg)
{

	/* boundaries check
	 *
	 * VTABLE_ELEMENT_NB(vtable_t) = __APCIE1532_UPPER_IOCTL_CMD +1
	 * maximum index value = __APCIE1532_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 apcie1532_do_dummy(pdev,cmd,arg);

	/* call actual ioctl handler - should be safe now */
	return (apcie1532_vtable[_IOC_NR(cmd)]) (pdev, cmd, arg);
}

//------------------------------------------------------------------------------
int do_CMD_APCIE1532_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_APCIE1532_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_APCIE1532_MAX_BOARD_NBR+1 * sizeof(char),GFP_KERNEL);
    if (!b_SlotArray)
	{
		/* bad param. Let output some information for debug */
		printk("%s: CMD_APCIE1532_CheckAndGetPCISlotNumber: -EINVAL\n",__DRIVER_NAME);
		return 0;
	}

	memset(b_SlotArray,0,CONFIG_APCIE1532_MAX_BOARD_NBR+1 );

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

			struct pci_dev * dev =  apcie1532_lookup_board_by_index(i);

			if (!dev) break;

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

		}
    }
    b_SlotArray[CONFIG_APCIE1532_MAX_BOARD_NBR] = apcie1532_majornumber;

    if ( copy_to_user ( (char __user * )arg, b_SlotArray, sizeof (BYTE[CONFIG_APCIE1532_MAX_BOARD_NBR+1])) )
	{
    	/* bad address. Let output some information for debug */
    	printk("%s: CMD_APCIE1532_CheckAndGetPCISlotNumber: -EFAULT\n",__DRIVER_NAME);
    	kfree(b_SlotArray);
    	return 0;
	}
	/* return the smaller value between CONFIG_APCIE1532_MAX_BOARD_NBR and number of board - Note: apcie1532_count is assumed to to be always consistent with the system PCI devices list */
    retval = ( atomic_read(&apcie1532_count) < CONFIG_APCIE1532_MAX_BOARD_NBR ? atomic_read(&apcie1532_count) : CONFIG_APCIE1532_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_APCIE1532_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] = GET_BAR3(pdev);;
	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_APCIE1532_SetBoardInformation(struct pci_dev * pdev, unsigned int cmd, unsigned long arg)
{
	return 0;
}
