/** @par LICENCE
 * @author Jordan Rose
* @verbatim
Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code of this module.

        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 should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

You shoud also find the complete GPL in the COPYING file
accompanying this source code.

* @endverbatim
*/

#include <linux/compiler.h> // Un/Likely
#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 "apci3120-private.h"


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

EXPORT_NO_SYMBOLS;

/* try to atomically retrieve _one_ event */
static inline unsigned int apci3120_evt_retrieve_atomic(struct pci_dev * pdev, apci3120_event_t * evt)
{
	unsigned long irqstate;
	unsigned int retrieved = 0;
	APCI3120_LOCK(pdev,&irqstate);
	{
		retrieved = apci3120_evt_retrieve(pdev,evt,1);
	}
	APCI3120_UNLOCK(pdev,irqstate);
	return retrieved;
}

/**
 * @brief retrive chuncks of data
 *
 * @param pdev Board
 * @param evt pointer to event table
 * @param size size we will try to get
 * @return unsigned int size read
 */
static inline unsigned int apci3120_evt_retrieve_multiple(struct pci_dev * pdev, apci3120_event_t * evt, size_t size)
{
	unsigned long irqstate;
	unsigned int retrieved = 0;
	APCI3120_LOCK(pdev,&irqstate);
	{
		retrieved = apci3120_evt_retrieve(pdev,evt,size);
	}
	APCI3120_UNLOCK(pdev,irqstate);
	return retrieved;
}

/* Empty the event FIFO - takes no arguments */
int do_CMD_apci3120_EventEmpty(struct pci_dev *pdev, unsigned int cmd, unsigned long arg)
{
	unsigned long irqstate;
	APCI3120_LOCK(pdev,&irqstate);
	{
		apci3120_event_t evt;
		while( apci3120_evt_count(pdev) )
		{
			apci3120_evt_retrieve(pdev,&evt,1);
		}
	}
	APCI3120_UNLOCK(pdev,irqstate);
	return 0;
}

/* Makes the caller sleep until there is an event */
int do_CMD_apci3120_EventWait(struct pci_dev *pdev, unsigned int cmd, unsigned long arg)
{
	// An update was made to this function, that send the entire event sequence in one go, instead of one event per call.
	apci3120_event_t* evt;
	size_t number_of_event;
	unsigned int retrieved = 0;
	int ret = 0;

	// Due to verifications at initialisation, we are guaranteed that number_of_acquisition % sequence_array_size == 0
	number_of_event = APCI3120_PRIVDATA(pdev)->sequence_data.number_of_acquisition / APCI3120_PRIVDATA(pdev)->sequence_array_size;

	evt = (apci3120_event_t*)kzalloc(number_of_event * sizeof(apci3120_event_t), GFP_KERNEL); // kzalloc also initialize memory to zero
	if (evt == NULL)
	{
		pr_err("apci3120_EventWait: kzalloc failed. Try reducing the number of acquisitions.\n");
		return -EFBIG;
	}
	// Fetch number_of_event events and if all of it was retrived, skip the waiting.
	// This can happen if we missed the event wake up. And if missed it multiple time, we can retrive all the data we missed out.
	// In the edge case scenario that the kernel is writing to the fifo while this function is called,
	// and hasn't reached the wait, we also need to partially recover data. But normally it shouldn't happen because of the lock inside the reading of data.
	// In most scenarios, the first apci3120_evt_retrieve_multiple call will instantly return 0, that will end up calling the wait
	// and the second try of apci3120_evt_retrieve_multiple, which in turn will recover the data.
	retrieved = apci3120_evt_retrieve_multiple(pdev, evt, number_of_event);
	if (likely(retrieved != number_of_event)) {
		ret = apci3120_evt_wait(pdev);
		if (ret) {
			kfree(evt);
			return ret;
		}
		// Get the missing events, most likely all event.
		retrieved += apci3120_evt_retrieve_multiple(pdev, &evt[retrieved], (number_of_event - retrieved));
	}

	if (copy_to_user((apci3120_event_t __user *) arg, evt, sizeof(apci3120_event_t) * retrieved))
	{
		/* Problem happened in the user_copy */
		kfree(evt);
		return -EFAULT;
	}
	kfree(evt);

	if(likely(retrieved == number_of_event)) /* an event was already available */
	{
		/* We've read everything, no error */
		return 0;
	}
	else {
		// In case some data is missing return total valid data. Notice the positive value; in our context other errors are negative.
		return retrieved;
	}
}
