/*
 * Copyright (C) Mar 2012 Mellanox Technologies Ltd. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *  Version: $Id: $
 *
 */


#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/io.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)
//"linux/uaccess.h" was added in 2.6.18
#include <linux/uaccess.h>
#else
#include <asm/uaccess.h>
#endif
#include <linux/pci.h>
#include <linux/version.h>

#include "driver_common.h"
#include "mst_pci.h"

#define  INIT   PCI_INIT
#define  STOP   PCI_STOP
#define  PCI_PARAMS_ PCI_PARAMS
#define  CONNECTX_WA PCI_CONNECTX_WA

static char* name="mst_pci";

#if CONFIG_COMPAT && !HAVE_COMPAT_IOCTL
static unsigned int
ioctl_cmds[] ={
	INIT,
	PCI_PARAMS_,
	CONNECTX_WA
};
#endif

/* Allow minor numbers 0-255 */
#define MAXMINOR 256
#define BUFFER_SIZE 256

MODULE_AUTHOR("Adrian Chiris - Mellanox Technologies LTD");
MODULE_DESCRIPTION("Mellanox configuration registers access driver (pci memory)");

#ifdef RETPOLINE_MLNX
MODULE_INFO(retpoline, "Y");
#endif

struct dev_data {
	struct pci_dev* pci_dev;
	unsigned long long bar;
	volatile ssize_t size;
	struct semaphore sem;
	char buf[BUFFER_SIZE];
	int bufused;
	unsigned char connectx_wa_slots;
};

static struct dev_data devices[MAXMINOR];

static int ioctl (struct inode *inode, struct file *file, unsigned int opcode, unsigned long udata_l)
{
	void* udata=(void*)udata_l;
	int minor=MINOR(inode->i_rdev);
	struct dev_data* dev=&devices[minor];
	int ret=0;

	/* By convention, any user gets read access
	 * and is allowed to use the device.
	 * Commands with no direction are administration
	 * commands, and you need write permission
	 * for this */

	if ( _IOC_DIR(opcode) == _IOC_NONE ) {
		if (! ( file->f_mode & FMODE_WRITE) ) return -EPERM;
	} else {
		if (! ( file->f_mode & FMODE_READ) ) return -EPERM;
	}

	if (down_interruptible(&devices[minor].sem)) {
		return -ERESTARTSYS;
	}


	// printk("<1> --------------------- mst - ioctl (%d)\n", opcode);
	switch (opcode) {

	case INIT:
		{
			struct mst_pci_init_st initd;
			struct pci_bus* bus = NULL;
			/* Now have to init the new device */

			/* printk("<1> --------------------- ioctl INIT: copy_from_user(%p, %p/%lx/, %d)\n", */
			/*        &initd, udata, udata_l, (int)sizeof(initd)); */
			/* printk("<1> --------------------- copy_from_user:%d\n", */
			/*        (int)copy_from_user(&initd, udata, (int)sizeof(initd))); */
			if (copy_from_user(&initd, udata, sizeof(initd))) {
				ret=-EFAULT;
				goto fin;
			}

			if (initd.domain == -1) {
				initd.domain = 0;
			}
			bus = pci_find_bus(initd.domain, initd.bus);

			if (!bus) {
				ret=-ENXIO;
				goto fin;
			}
			dev->pci_dev = NULL;
			dev->pci_dev = pci_get_slot (bus, initd.devfn);

			if (!dev->pci_dev) {
				ret=-ENXIO;
				goto fin;
			}

			if (initd.bar >= DEVICE_COUNT_RESOURCE) {
				ret=-ENXIO;
				goto fin;
			}

			dev->bar  = dev->pci_dev->resource[initd.bar].start;
			dev->size = dev->pci_dev->resource[initd.bar].end + 1 - dev->pci_dev->resource[initd.bar].start;
			if (dev->size == 1) {
				dev->size = 0;
			}

			dev->bufused=0;
			goto fin;
		}

	case STOP:
		pci_dev_put(dev->pci_dev);
		devices[minor].size=0;
		goto fin;

	case CONNECTX_WA:
		{
			struct mst_data* md = (struct mst_data*)file->private_data;
			if (md->connectx_wa_slot_p1) {
			    ret = -EPERM;
			    goto fin;
			}
			/* find first un(set) bit. */
			md->connectx_wa_slot_p1 = ffs(~dev->connectx_wa_slots);
			if (md->connectx_wa_slot_p1 == 0 || md->connectx_wa_slot_p1 > CONNECTX_WA_SIZE) {
				ret = -ENOLCK;
			} else {
				unsigned int slot_mask = 1 << (md->connectx_wa_slot_p1 - 1);
				dev->connectx_wa_slots |= slot_mask;

				// printk("MST_PCI: ioctl CONNECTX_WA: Took slot %u. Current slots: %02x\n", md->connectx_wa_slot_p1 - 1, dev->connectx_wa_slots);
				if (copy_to_user(udata, &md->connectx_wa_slot_p1, sizeof(md->connectx_wa_slot_p1))) {
					ret = -EFAULT;
				}
			}

			goto fin;
		}

	case PCI_PARAMS_:
		{
			struct mst_pci_params_st paramsd;
			paramsd.bar=dev->bar;
			paramsd.size=dev->size;

			if (copy_to_user(udata, &paramsd, sizeof(paramsd))) {
				ret=-EFAULT;
			}
			goto fin;
		}

	default:
		ret= -ENOTTY;
		goto fin;
	}

	fin:
	up(&devices[minor].sem);
	return ret;
}

/*
 * Architectures vary in how they handle caching for addresses
 * outside of main memory.
 */
static inline int noncached_address(unsigned long addr)
{
#if defined(__i386__)
	/*
	 * On the PPro and successors, the MTRRs are used to set
	 * memory types for physical addresses outside main memory,
	 * so blindly setting PCD or PWT on those pages is wrong.
	 * For Pentiums and earlier, the surround logic should disable
	 * caching for the high addresses through the KEN pin, but
	 * we maintain the tradition of paranoia in this code.
	 */
	return !( test_bit(X86_FEATURE_MTRR, (void *)&boot_cpu_data.x86_capability) ||
		  test_bit(X86_FEATURE_K6_MTRR, (void *)&boot_cpu_data.x86_capability) ||
		  test_bit(X86_FEATURE_CYRIX_ARR, (void *)&boot_cpu_data.x86_capability) ||
		  test_bit(X86_FEATURE_CENTAUR_MCR, (void *)&boot_cpu_data.x86_capability) )
	&& addr >= __pa(high_memory);
#else
	return addr >= __pa(high_memory);
#endif
}

static int mmap(struct file *file, struct vm_area_struct *vma)
{

	int ret=0;
	unsigned long off;
	unsigned long vsize;
	unsigned long long offset;
	struct dev_data* dev=((struct mst_data*)file->private_data)->dev;

	if (!dev) return -ENOTTY; /*Shouldn't happend, as open assigns it
				    but just in case */

	if (down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	off = vma->vm_pgoff << PAGE_SHIFT;
	vsize = vma->vm_end - vma->vm_start;

	if ((dev->size <= off) || (dev->size < off+vsize)) {
		ret=-EINVAL;
		goto fin;
	}

	offset = dev->bar+off;
	/*
	 *    * Accessing memory above the top the kernel knows about or
	 *    * through a file pointer that was marked O_SYNC will be
	 *    * done non-cached.
	 *    */
	if (noncached_address(offset) || (file->f_flags & O_SYNC))
		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

	ret=io_remap_pfn_range(vma, vma->vm_start, offset >> PAGE_SHIFT,
			       vsize, vma->vm_page_prot);

	if (ret) goto fin;

	fin:
	up(&dev->sem);
	return ret;
}


static int mst_pci_release(struct inode* inode, struct file* file) {
	struct mst_data* md = file->private_data;

	if (md->connectx_wa_slot_p1) {
		unsigned int mask = 0;

		if (down_interruptible(&md->dev->sem)) {
			return -ERESTARTSYS;
		}

		mask = ~(1 << (md->connectx_wa_slot_p1 - 1));
		md->dev->connectx_wa_slots &= mask;

		// printk("MST_PCI: released slot %ud. Current slots: %02x\n", md->connectx_wa_slot_p1 - 1 , md->dev->connectx_wa_slots);
		md->connectx_wa_slot_p1 = 0;
		up(&md->dev->sem);
	}

	return 0;
}

#define SUPPORT_MMAP 1
#define MST_PCI_DRIVER
#include "driver_common.c"

