/*
 * 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/delay.h>

#include "driver_common.h"
#include "mst_pciconf.h"
#define  INIT           PCICONF_INIT
#define  STOP           PCICONF_STOP
#define  READ4          PCICONF_READ4
#define  READ4_NEW      PCICONF_READ4_NEW
#define  WRITE4         PCICONF_WRITE4
#define  WRITE4_NEW     PCICONF_WRITE4_NEW
#define  MODIFY         PCICONF_MODIFY
#define  READ4_BUFFER   PCICONF_READ4_BUFFER
#define  WRITE4_BUFFER  PCICONF_WRITE4_BUFFER
#define  MST_PARAMS     PCICONF_MST_PARAMS

static char* name="mst_pciconf";

#if defined(CONFIG_COMPAT) && CONFIG_COMPAT && !(defined(HAVE_COMPAT_IOCTL) && HAVE_COMPAT_IOCTL)
static unsigned int
ioctl_cmds[] ={
	INIT,
	READ4,
	WRITE4,
	MODIFY,
	READ4_OLD,
	WRITE4_OLD,
	MODIFY_OLD
};
#endif

/* Allow minor numbers 0-255 */
#define MAXMINOR 256
#define BUFFER_SIZE 256
#define MLNX_VENDOR_SPECIFIC_CAP_ID 0x9
#define CRSPACE_DOMAIN 0x2
#define AS_ICMD      0x3
#define AS_CR_SPACE  0x2
#define AS_SEMAPHORE 0xa

MODULE_AUTHOR("Oren Kladnitsky - Mellanox Technologies LTD");
MODULE_DESCRIPTION("Mellanox configuration registers access driver (pci conf)");

struct dev_data {
	struct pci_dev* pci_dev;
	int addr_reg;
	int data_reg;
	int wo_addr;
	volatile int valid; // dotanb-cr: use pci_dev as valid
	struct semaphore sem;
	char buf[BUFFER_SIZE];
	int bufused;
	/* Vendor specific capability address */
	int vendor_specific_cap;
	/* status on VSEC supported spaces*/
	int spaces_support_status;
};

static struct dev_data devices[MAXMINOR]; // dotanb-cr: use dynamic alloc

/*
 * Read/Write CR-Space (Old Cap)
 */
static int write4_old(struct dev_data* dev, unsigned int offset, unsigned int data)
{
    int ret;

    if (dev->wo_addr) {
        /*
         * Write operation with new WO GW
         * 1. Write data
         * 2. Write address
         */

        //printk("WRITE DATA: %#x, TO %#x\n", data, dev->data_reg);
        ret=pci_write_config_dword(dev->pci_dev, dev->data_reg, data);
        if (ret) return ret;
        //printk("WRITE ADDR: %#x, to %#x\n", offset, dev->addr_reg);
        ret=pci_write_config_dword(dev->pci_dev, dev->addr_reg, offset);
    } else {
        ret=pci_write_config_dword(dev->pci_dev, dev->addr_reg, offset);
        if (ret) return ret;

        ret=pci_write_config_dword(dev->pci_dev, dev->data_reg, data);
    }
    return ret;
}

static int read4_old(struct dev_data* dev, unsigned int offset, unsigned int* data)
{
    int ret;
    unsigned int newOffset = offset;
    if (dev->wo_addr) {
        /*
         * Read operation, Address LSB should be 1
         */
        newOffset = offset | 0x1;
    }
    //printk("WRITE ADDR: %#x, to %#x\n", newOffset, dev->addr_reg);
    ret=pci_write_config_dword(dev->pci_dev, dev->addr_reg, newOffset);
    if (ret) return ret;

    ret=pci_read_config_dword(dev->pci_dev, dev->data_reg, data);
    //printk("READ DATA: %#x, from %#x\n", *data, dev->data_reg);
    return ret;
}

/*
 * Read/Write Address-Domain (New Cap)
 */

// BIT Slicing macros
#define ONES32(size)                    ((size)?(0xffffffff>>(32-(size))):0)
#define MASK32(offset,size)             (ONES32(size)<<(offset))

#define EXTRACT_C(source,offset,size)   ((((unsigned)(source))>>(offset)) & ONES32(size))
#define EXTRACT(src,start,len)          (((len)==32)?(src):EXTRACT_C(src,start,len))

#define MERGE_C(rsrc1,rsrc2,start,len)  ((((rsrc2)<<(start)) & (MASK32((start),(len)))) | ((rsrc1) & (~MASK32((start),(len)))))
#define MERGE(rsrc1,rsrc2,start,len)    (((len)==32)?(rsrc2):MERGE_C(rsrc1,rsrc2,start,len))


/* PCI address space related enum*/
enum {
    PCI_CAP_PTR = 0x34,
    PCI_HDR_SIZE = 0x40,
    PCI_EXT_SPACE_ADDR = 0xff,

    PCI_CTRL_OFFSET = 0x4, // for space / semaphore / auto-increment bit
    PCI_COUNTER_OFFSET = 0x8,
    PCI_SEMAPHORE_OFFSET = 0xc,
    PCI_ADDR_OFFSET = 0x10,
    PCI_DATA_OFFSET = 0x14,

    PCI_FLAG_BIT_OFFS = 31,

    PCI_SPACE_BIT_OFFS = 0,
    PCI_SPACE_BIT_LEN = 16,

    PCI_STATUS_BIT_OFFS = 29,
    PCI_STATUS_BIT_LEN = 3,
};

/* Mellanox vendor specific enum */
enum {
    CAP_ID = 0x9,
    IFC_MAX_RETRIES = 0x10000,
    SEM_MAX_RETRIES = 0x1000
};

/* PCI operation enum(read or write)*/
enum {
    READ_OP = 0,
    WRITE_OP = 1,
};

/* VSEC space status enum*/
enum {
    SS_UNINITIALIZED = 0,
    SS_ALL_SPACES_SUPPORTED = 1,
    SS_NOT_ALL_SPACES_SUPPORTED = 2
};

// VSEC supported macro
#define VSEC_FULLY_SUPPORTED(dev) (((dev)->vendor_specific_cap) && ((dev)->spaces_support_status == SS_ALL_SPACES_SUPPORTED))

static int _vendor_specific_sem(struct dev_data* dev, int state)
{
    u32 lock_val;
    u32 counter = 0;
    int retries = 0;
    int ret;
    if (!state) {// unlock
        ret = pci_write_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_SEMAPHORE_OFFSET, 0);
        if (ret) return ret;
    } else { // lock
        do {
            if (retries > SEM_MAX_RETRIES) {
                return -1;
            }
            // read semaphore untill 0x0
            ret = pci_read_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_SEMAPHORE_OFFSET, &lock_val);
            if (ret) return ret;

            if (lock_val) { //semaphore is taken
                retries++;
                msleep(1); // wait for current op to end
                continue;
            }
            //read ticket
            ret = pci_read_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_COUNTER_OFFSET, &counter);
            if (ret) return ret;
            //write ticket to semaphore dword
            ret = pci_write_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_SEMAPHORE_OFFSET, counter);
            if (ret) return ret;
            // read back semaphore make sure ticket == semaphore else repeat
            ret = pci_read_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_SEMAPHORE_OFFSET, &lock_val);
            if (ret) return ret;
            retries++;
        } while (counter != lock_val);
    }
    return 0;
}

static int _wait_on_flag(struct dev_data* dev, u8 expected_val)
{
    int retries = 0;
    int ret;
    u32 flag;
    do {
         if (retries > IFC_MAX_RETRIES) {
             return -1;
         }

         ret = pci_read_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_ADDR_OFFSET, &flag);
         if (ret) return ret;

         flag = EXTRACT(flag, PCI_FLAG_BIT_OFFS, 1);
         retries++;
         if ((retries & 0xf) == 0) {// dont sleep always
             //usleep_range(1,5);
         }
     } while (flag != expected_val);
    return 0;
}

static int _set_addr_space(struct dev_data* dev, u16 space)
{
    // read modify write
    u32 val;
    int ret;
    ret = pci_read_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_CTRL_OFFSET, &val);
    if (ret) return ret;
    val = MERGE(val, space, PCI_SPACE_BIT_OFFS, PCI_SPACE_BIT_LEN);
    ret = pci_write_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_CTRL_OFFSET, val);
    if (ret) return ret;
    // read status and make sure space is supported
    ret = pci_read_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_CTRL_OFFSET, &val);
    if (ret) return ret;

    if (EXTRACT(val, PCI_STATUS_BIT_OFFS, PCI_STATUS_BIT_LEN) == 0) {
        //printk("[MST]: CRSPACE %d is not supported !\n", space);
        return -1;
    }
    //printk("[MST]: CRSPACE %d is supported !\n", space);
    return 0;
}

static int _pciconf_rw(struct dev_data* dev, unsigned int offset, u32* data, int rw)
{
    int ret = 0;
    u32 address = offset;

    //last 2 bits must be zero as we only allow 30 bits addresses
    if (EXTRACT(address, 30, 2)) {
        return -1;
    }

    address = MERGE(address,(rw ? 1 : 0), PCI_FLAG_BIT_OFFS, 1);
    if (rw == WRITE_OP) {
        // write data
        ret = pci_write_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_DATA_OFFSET, *data);
        if (ret) return ret;
        // write address
        ret = pci_write_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_ADDR_OFFSET, address);
        if (ret) return ret;
        // wait on flag
        ret = _wait_on_flag(dev, 0);
    } else {
        // write address
        ret = pci_write_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_ADDR_OFFSET, address);
        if (ret) return ret;
        // wait on flag
        ret = _wait_on_flag(dev, 1);
        // read data
        ret = pci_read_config_dword(dev->pci_dev, dev->vendor_specific_cap + PCI_DATA_OFFSET, data);
        if (ret) return ret;
    }
    return ret;
}

static int _send_pci_cmd_int(struct dev_data* dev, int space, unsigned int offset, u32* data, int rw)
{
    int ret = 0;

    // take semaphore
    ret = _vendor_specific_sem(dev, 1);
    if (ret) {
        return ret;
    }
    // set address space
    ret = _set_addr_space(dev, space);
    if (ret) {
        goto cleanup;
    }
    // read/write the data
    ret = _pciconf_rw(dev, offset, data, rw);
cleanup:
    // clear semaphore
    _vendor_specific_sem(dev, 0);
    return ret;
}

static int _block_op(struct dev_data* dev, int space, unsigned int offset, int size, u32* data, int rw)
{
    int i;
    int ret = 0;
    int wrote_or_read = size;
    if (size % 4) {
        return -1;
    }
    // lock semaphore and set address space
    ret = _vendor_specific_sem(dev, 1);
    if (ret) {
         return -1;
    }
    // set address space
    ret = _set_addr_space(dev, space);
    if (ret) {
        wrote_or_read = -1;
        goto cleanup;
    }

    for (i = 0; i < size ; i += 4) {
        if (_pciconf_rw(dev, offset + i, &(data[(i >> 2)]), rw)) {
            wrote_or_read = i;
            goto cleanup;
        }
    }
cleanup:
    _vendor_specific_sem(dev, 0);
    return wrote_or_read;
}

static int write4_new(struct dev_data* dev, int addresss_domain, unsigned int offset, unsigned int data)
{
    int ret;

    ret = _send_pci_cmd_int(dev, addresss_domain, offset, &data, WRITE_OP);
    if (ret) {
        return -1;
    }
    return 0;
}

static int read4_new(struct dev_data* dev, int address_space, unsigned int offset, unsigned int* data)
{
    int ret;

    ret = _send_pci_cmd_int(dev, address_space, offset, data, READ_OP);
    if (ret) {
        return -1;
    }
    return 0;
}

static int write4_block_new(struct dev_data* dev, int address_space, unsigned int offset, int size, u32* data)
{
    return _block_op(dev, address_space, offset, size, data, WRITE_OP);
}

static int read4_block_new(struct dev_data* dev, int address_space, unsigned int offset, int size, u32* data)
{
    return _block_op(dev, address_space, offset, size, data, READ_OP);
}

static int get_space_support_status(struct dev_data* dev)
{
    int ret;
    //printk("[MST] Checking if the Vendor CAP %d supports the SPACES in devices\n", vend_cap);
    if (!dev->vendor_specific_cap) {
        return 0;
    }
    if (dev->spaces_support_status != SS_UNINITIALIZED ) {
        return 0;
    }
    // take semaphore
    ret = _vendor_specific_sem(dev, 1);
    if (ret) {
        //printk("[MST] Failed to lock semaphore\n");
        return 1;
    }

    if( _set_addr_space(dev, AS_CR_SPACE) ||
       _set_addr_space(dev, AS_ICMD)     ||
       _set_addr_space(dev, AS_SEMAPHORE)  ) {
        //printk("[MST] At least one SPACE is not supported\n");
        dev->spaces_support_status = SS_NOT_ALL_SPACES_SUPPORTED;
    } else {
        dev->spaces_support_status = SS_ALL_SPACES_SUPPORTED;
    }
    // clear semaphore
    _vendor_specific_sem(dev, 0);
    return 0;
}
/*
 * End of Capabilities section
 */
#define WO_REG_ADDR_DATA 0xbadacce5
#define DEVID_OFFSET     0xf0014
int is_wo_gw(struct pci_dev* pcidev, unsigned addr_reg)
{
    int ret;
    unsigned int data = 0;
    ret = pci_write_config_dword(pcidev, addr_reg, DEVID_OFFSET);
    if (ret) {
        return 0;
    }
    ret = pci_read_config_dword(pcidev, addr_reg, &data);
    if (ret) {
        return 0;
    }
    if ( data == WO_REG_ADDR_DATA ) {
        return 1;
    }
    return 0;
}

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;
	unsigned int d;

	/* 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;
	}

	switch (opcode) {

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

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

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

			if (!bus) {
				ret=-ENXIO;
				goto fin;
			}

			dev->pci_dev = pci_get_slot (bus, initd.devfn);

			if (!dev->pci_dev) {
				ret=-ENXIO;
				goto fin;
			}
            dev->valid=1;
			dev->bufused=0;
			/* Old Cap for CR-Access*/
			dev->addr_reg=initd.addr_reg;
			dev->data_reg=initd.data_reg;
			/*
			 * Check LiveFish GW mode
			 */
			dev->wo_addr = is_wo_gw(dev->pci_dev, initd.addr_reg);
//			printk("%04x:%02x:%02x.%0x - ADDR REG WO: %d\n", pci_domain_nr(dev->pci_dev->bus),
//			        dev->pci_dev->bus->number, PCI_SLOT(dev->pci_dev->devfn),
//			        PCI_FUNC(dev->pci_dev->devfn), dev->wo_addr);
			/* New Cap for CR-Access*/
			dev->vendor_specific_cap = pci_find_capability(dev->pci_dev, MLNX_VENDOR_SPECIFIC_CAP_ID);
			dev->spaces_support_status = SS_UNINITIALIZED; // init on first op
			goto fin;
		}

	case STOP:
	    if (dev->valid) {
	        pci_dev_put(dev->pci_dev);
	    }
		dev->valid=0;
		goto fin;

	case WRITE4_NEW:
	{
	    struct mst_write4_new_st write4d;
        if (!dev->valid) {
            ret=-ENOTTY;
            goto fin;
        }

        if (copy_from_user(&write4d, udata, sizeof(write4d))) {
            ret=-EFAULT;
            goto fin;
        }
        if (get_space_support_status(dev)) {
            ret=-EBUSY;
            goto fin;
        }
        if ( VSEC_FULLY_SUPPORTED(dev) ) {
            ret = write4_new(dev, write4d.address_space, write4d.offset, write4d.data);
        } else {
            ret = write4_old(dev, write4d.offset, write4d.data);
        }
        goto fin;
	}
	case WRITE4:
		{
			struct mst_write4_st write4d;
            if (!dev->valid) {
                ret=-ENOTTY;
                goto fin;
            }

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

            ret = write4_old(dev, write4d.offset, write4d.data);

			goto fin;
		}

	case READ4_NEW:
	{
        struct mst_read4_new_st read4d;
        struct mst_read4_new_st* r_udata=(struct mst_read4_new_st*)udata;

        if (!dev->valid) {
            ret=-ENOTTY;
            goto fin;
        }

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

        if (get_space_support_status(dev)) {
            ret=-EBUSY;
            goto fin;
        }

        if ( VSEC_FULLY_SUPPORTED(dev) ) {
            ret = read4_new(dev, read4d.address_space, read4d.offset, &d);
        } else {
            ret = read4_old(dev, read4d.offset, &d);
        }
        if (ret) goto fin;
        ret=put_user(d,&(r_udata->data))?-EFAULT:0;
        goto fin;
	}
	case READ4:
	{
        struct mst_read4_st read4d;
        struct mst_read4_st* r_udata=(struct mst_read4_st*)udata;

        if (!dev->valid) {
            ret=-ENOTTY;
            goto fin;
        }

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

        ret = read4_old(dev, read4d.offset, &d);
        if (ret) goto fin;
        ret=put_user(d,&(r_udata->data))?-EFAULT:0;
        goto fin;
	}
	case MODIFY:
		{
			struct mst_modify_st modifyd;
			struct mst_modify_st* m_udata=(struct mst_modify_st*)udata;

            if (!dev->valid) {
                ret=-ENOTTY;
                goto fin;
            }
			if (copy_from_user(&modifyd, udata, sizeof(modifyd))) {
				ret=-EFAULT;
				goto fin;
			}

	        if (get_space_support_status(dev)) {
	            ret=-EBUSY;
	            goto fin;
	        }

            // Read
            if (VSEC_FULLY_SUPPORTED(dev)) {
                ret = read4_new(dev, modifyd.address_space, modifyd.offset, &d);
            } else {
                ret = read4_old(dev, modifyd.offset, &d);
            }
			if (ret) goto fin;
            // Modify
			d= (d & ~modifyd.mask) | (modifyd.data & modifyd.mask);
            // Write
            if (VSEC_FULLY_SUPPORTED(dev)) {
                ret = write4_new(dev, modifyd.address_space, modifyd.offset, d);
            } else {
                ret = write4_old(dev, modifyd.offset, d);
            }
            if (ret) goto fin;

            ret=put_user(d,&(m_udata->old_data))?-EFAULT:0;
            goto fin;
        }
    case READ4_BUFFER:
    {
        struct mst_read4_buffer_st read4_buf;
        struct mst_read4_buffer_st* rb_udata=(struct mst_read4_buffer_st*)udata;

        if (!dev->valid) {
            ret=-ENOTTY;
            goto fin;
        }

        if (get_space_support_status(dev)) {
            ret=-EBUSY;
            goto fin;
        }

        if (dev->spaces_support_status != SS_ALL_SPACES_SUPPORTED) {
            ret = -ENOSYS;
            goto fin;
        }


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

        ret = read4_block_new(dev, read4_buf.address_space, read4_buf.offset, read4_buf.size, read4_buf.data);
        if (ret != read4_buf.size) goto fin;

        ret = copy_to_user(rb_udata, &read4_buf, sizeof(read4_buf)) ? -EFAULT : read4_buf.size;
        goto fin;
    }
    case WRITE4_BUFFER:
    {
        struct mst_write4_buffer_st write4_buf;
        struct mst_write4_buffer_st* wb_udata=(struct mst_write4_buffer_st*)udata;

        if (!dev->valid) {
            ret=-ENOTTY;
            goto fin;
        }

        if (get_space_support_status(dev)) {
            ret=-EBUSY;
            goto fin;
        }

        if (dev->spaces_support_status != SS_ALL_SPACES_SUPPORTED) {
            ret = -ENOSYS;
            goto fin;
        }


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

        ret = write4_block_new(dev, write4_buf.address_space, write4_buf.offset, write4_buf.size, write4_buf.data);
        if (ret != write4_buf.size) goto fin;

        ret = copy_to_user(wb_udata, &write4_buf, sizeof(write4_buf)) ? -EFAULT : write4_buf.size;
        goto fin;
    }
    case MST_PARAMS:
    {
        struct mst_params_st params;
        struct mst_params_st* params_udata = (struct mst_params_st*)udata;
        if (!dev->valid) {
            ret=-ENOTTY;
            goto fin;
        }
        // best effort : try to get space spport status if we fail assume we got vsec support.
        get_space_support_status(dev);

        params.bus = dev->pci_dev->bus->number;
        params.bar = 0;
        params.domain = pci_domain_nr(dev->pci_dev->bus);
        params.func = PCI_FUNC(dev->pci_dev->devfn);
        params.slot = PCI_SLOT(dev->pci_dev->devfn);
        params.device = dev->pci_dev->device;
        params.vendor = dev->pci_dev->vendor;
        params.subsystem_device = dev->pci_dev->subsystem_device;
        params.subsystem_vendor = dev->pci_dev->subsystem_vendor;
        if (dev->vendor_specific_cap && (dev->spaces_support_status == SS_ALL_SPACES_SUPPORTED ||
                                                              dev->spaces_support_status == SS_UNINITIALIZED)) { // assume supported if SS_UNINITIALIZED (since semaphore is locked)
            params.vendor_specific_cap = dev->vendor_specific_cap;
        } else {
            params.vendor_specific_cap = 0;
        }
        params.multifunction = dev->pci_dev->multifunction;
        ret = copy_to_user(params_udata, &params, sizeof(params));
        goto fin;
    }
    case PCICONF_VPD_READ4:
#if (LINUX_VERSION_CODE >= 0x02061D)
    {
        struct mst_vpd_read4_st vpd_read4;
        struct mst_vpd_read4_st* r_udata = (struct mst_vpd_read4_st*)udata;
        if (!dev->valid) {
            ret=-ENOTTY;
            goto fin;
        }

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

        ret = pci_read_vpd(dev->pci_dev, vpd_read4.offset, 4, &d);
        if (ret < 1) {
            ret=-EFAULT;
            goto fin;
        }
        ret = put_user(d,&(r_udata->data))?-EFAULT:0;
        goto fin;
    }
#endif
    case PCICONF_VPD_WRITE4:
#if (LINUX_VERSION_CODE >= 0x02061D)
    {
        struct mst_vpd_write4_st vpd_write4;

        if (!dev->valid) {
            ret=-ENOTTY;
            goto fin;
        }

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

        ret = pci_write_vpd(dev->pci_dev, vpd_write4.offset, 4, &vpd_write4.data);
        goto fin;
    }
#endif
	default:
		ret= -ENOTTY;
		goto fin;
	}

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

#include "driver_common.c"


