/***************************************************************************************\

File Name:      mtx_fops.c

Description:    File operations on a MTX device file.

References:     None.

Author:         Karl Lessard    <klessard@matrox.com>

    Copyright (c) 2001, Matrox Graphics Inc.
    All Rights Reserved.

\***************************************************************************************/

#define __NO_VERSION__
#include "mtx_drv.h"

/***************************************************************************************
 * Function:       mtx_open
 *
 * Description:    Open fop. Called whenever a process open a file controlled
 *                 by the MTX driver.
 *
 * Parameters:     inode               Storing device inode for this file.
 *                 filp                pointer to the file structure.
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       Minor numbers for a device file is a concatenation of the
 *                 PCI bus nb. and the PCI slot nb. of the hardware. For now,
 *                 minor numbers are only 8 bits values, so we can only support
 *                 up to 16 PCI buses and 16 devices. Eventually, minor numbers 
 *                 will be 16 bits long, so this inconvenience will be fixed.
 *
 *                 Once a process has opened a device, it cannot use it until
 *                 it has created a context for it, using the ioctl.
 */
int mtx_open(struct inode *inode, struct file *filp)
{
    mtx_file_t *priv;
    mtx_device_t *dev;
    unsigned int minor;
    int ret;

    /* try to retrieve MTX data from file ptr */
    priv = (mtx_file_t*)filp->private_data;
    if (priv != NULL) {
        MTX_ERROR("Device already opened by this file\n");
        return -EINVAL;
    }
        
    minor = fops_GetINodeMinor(inode);

    /* Get the MTX device from the device file minor number */
    dev = mtx_dev_find(MTX_MINOR_PCIBUS(minor), MTX_MINOR_PCISLOT(minor));
    if (!dev)
        return -EINVAL;

    /* Allocate structure for private file data */
    priv = MTXALLOC(sizeof(mtx_file_t));
    if (!priv)
        return -ENOMEM;
        
    filp->private_data = priv;

    /* Init private file data */
    priv->admin = capable(CAP_SYS_ADMIN) ? 1 : 0;
    priv->auth = 1;
    priv->dev = dev;
    
    /* Increment open count for this device */
    spin_lock(&dev->count_lock);
    if (!dev->open_count++) {
        
        spin_unlock(&dev->count_lock);

        /* This is the first reference to this device, initialize it now */
        if ((ret = mtx_dev_init(dev)) < 0) {

            mtx_release(inode, filp);
            return ret;
        }

        /* Dump memory statistics */
        mtx_mem_stats_dump();
        
    } else spin_unlock(&dev->count_lock);

    /* Init file context */
    if ((ret = mtx_ctx_init(dev, &priv->ctx)) < 0) {
        MTX_ERROR("Failed to add new context for device %u:%u\n", 
                  dev->pci_dev->bus->number, dev->pci_dev->devfn);
        return ret;
    }

    return 0;
}

/***************************************************************************************
 * Function:       mtx_release
 *
 * Description:    Release fop. Called whenever a process close the last reference to a 
 *                 opened MTX device file.
 *
 * Parameters:     inode               Storing device inode for this file.
 *                 filp                pointer to the file structure.
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 *
 */
int mtx_release(struct inode *inode, struct file *filp)
{
    mtx_file_t *priv = (mtx_file_t*)filp->private_data;
    mtx_device_t *dev;

    if (!priv) return -EINVAL;
 
    dev = priv->dev;
    if (dev) {

        /* Cleanup file context */
        mtx_ctx_cleanup(dev, &priv->ctx);

        /* Decrement open count for this device */
        spin_lock(&dev->count_lock);
        if (!--dev->open_count) {

            spin_unlock(&dev->count_lock);

            /* This is the last reference to this device,
            * clean it up now */
            mtx_dev_cleanup(dev);
        
            /* Dump memory statistics */
            mtx_mem_stats_dump();

        } else spin_unlock(&dev->count_lock);
    }
        
    /* Free file private data */
    MTXFREE(priv, sizeof(*priv));
    
    return 0;
}

/***************************************************************************************
 * Function:       mtx_ioctl
 *
 * Description:    Ioctl fop. Called whenever a process sends a IOCTL request to
 *                 the device file.
 *
 * Parameters:     inode               Storing device inode for this file.
 *                 filp                pointer to the file structure.
 *                 cmd                 ioctl command id
 *                 arg                 argument passed to ioctl command
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 *
 */
int mtx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
    mtx_file_t *priv = (mtx_file_t *)filp->private_data;
    unsigned int type = _IOC_TYPE(cmd);
    int nr = (int)_IOC_NR(cmd);
    mtx_ioctl_t *ioctl = NULL;

    if (!priv) return -EINVAL;
    
    /* Validate ioctl */
    if ((type != MTX_IOCTL_MAGIC) || (nr > MTX_IOCTL_MAXNR))
        return -ENOTTY;

    ioctl = &mtx_ioctls[nr];
       
    if (!ioctl->func.vd)
        return -ENOTTY;

    /* Check for user access */
    if ((ioctl->auth_needed && !priv->auth) || (ioctl->admin_only && !priv->admin))
        return -EACCES;

    if (ioctl->dispatch)
    {
        /* Dispatch ioctl to core driver if has valid context */
        if (priv->ctx.core_data == (MTXHANDLE)NULL)
            return -EINVAL;

        return ioctl->func.core(priv->ctx.core_data, cmd, arg);
    }
    
    /* Ioctl is processed by the mtx generic driver */
    return ioctl->func.mtx(priv, cmd, arg);
}

/***************************************************************************************
 * Function:       mtx_mmap
 *
 * Description:    Mapping a device to user-space memory, i.e. associating a range 
 *                 of user-space addresses to device memory. All regions a user
 *                 process can map has been previously initialized by the kernel
 *                 module, and can be referred in the memory_regions list for this
 *                 device.
 *
 * Parameters:     filp             pointer to the file structure
 *                 vma              pointer to the vma structure
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       
 */
int
mtx_mmap(struct file *filp, struct vm_area_struct *vma)
{
    mtx_file_t *priv = (mtx_file_t *)filp->private_data;
    mtx_device_t *dev = priv ? priv->dev : NULL;
    unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
    mtx_region_t* region = NULL;
    int retcode = -EINVAL;

    if (!dev)
        return -EINVAL;
 
    /* Check if the physical address really belong to us, by matching it
     * with a memory region for this device. (Note: only device regions can be mapped, 
     * and not individual memory block) */
    region = MtxRegion_FindFromBase(dev, offset);

    /* Don't map if no region matched */
    if (!region)
        return -EINVAL;
    
    /* Check if user have permissions to map this region */
    if ((region->scope == MTX_MEMSCOPE_KERNEL) ||   
        ((region->scope == MTX_MEMSCOPE_PRIVATE) && (region->pid != current->pid)) ||
        ((region->scope == MTX_MEMSCOPE_ROOT) && !(priv->admin)) ||
        ((region->scope == MTX_MEMSCOPE_AUTH) && !(priv->auth)))
        return -EPERM;
  
    switch (region->type) {

        case MTX_MEMTYPE_AGP:
            retcode = mtx_vm_map_agp(dev, region, vma); 
            break;

        case MTX_MEMTYPE_IO:
            retcode = mtx_vm_map_io(dev, region, vma);
            break;

        case MTX_MEMTYPE_PCI_LOCKED:
            if (filp->f_flags & O_SYNC) vma->vm_flags |= VM_IO;
            retcode = mtx_vm_map_pci(dev, region, vma);
            break;

        case MTX_MEMTYPE_SYS:
            retcode = mtx_vm_map_sys(dev, region, vma); 
            break;
            
        default:
            /* Memory type not supported by mmap */
            return -EINVAL;
    }

    if (!retcode)
        /* Call explicitly the open method */
        vma->vm_ops->open(vma);

    return retcode;
}
