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

File Name:      mtx_ioctl.c

Description:    IOCTLs implementation for MTX driver.

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_ioctl_get_memory_info
 *
 * Description:    Return important information about PCI resources and other
 *                 memory regions.
 *
 * Parameters:     priv                MTX private file data
 *                 cmd                 ioctl command
 *                 arg                 ioctl argument
 *                 
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 */
int mtx_ioctl_get_memory_info(mtx_file_t *priv, unsigned int cmd, unsigned long arg)
{
    mtx_device_t *dev = priv->dev;
    mtx_ioctl_get_memory_info_t mem_info;

    if (!dev) return -EINVAL;
    
    /* Fill up the ioctl structure with device information */
    mem_info.fb_base  = dev->framebuffer.base;
    mem_info.fb_size  = dev->framebuffer.size;
    mem_info.reg_base = dev->registers.base;
    mem_info.reg_size = dev->registers.size;

    if (agp_drv != NULL) {
        
        mem_info.agp_base = agp_drv->aperture_base;
        mem_info.agp_size = agp_drv->aperture_size;
        
    } else {
        
        mem_info.agp_base = 0;
        mem_info.agp_size = 0;
    }

    if ( copy_to_user((mtx_ioctl_get_memory_info_t *)arg, &mem_info, sizeof(mem_info)) )
        return -EFAULT;
        
    return 0;
}   

/***************************************************************************************
 * Function:       mtx_ioctl_get_buffer
 *
 * Description:    Return a memory buffer that can be used by the user process. The
 *                 buffer may be allocated in system, AGP or PCI contiguous memory.
 *                 The caller should also specified the scope of the buffer, that 
 *                 determine its sharing attributes. Supported scopes are:
 *
 *                 PRIVATE: The buffer is created and its use is private to the
 *                          process. The kernel still have read/write access to it,
 *                          so the process can request the driver for such operations
 *                          without problems, but needs to give information about
 *                          the buffer itself.
 *
 *                 ROOT:    This buffer is shared between the kernel and root processes 
 *                          only.
 *
 *                 AUTH:    This buffer is shared between the kernel and any 
 *                          authorized processes, such as root applications.
 *                          
 *                 ALL:     This buffer is shared to all processes, including the
 *                          kernel. The first process to get such buffer needs
 *                          authorization from the driver, such as root processes.
 *     
 * Parameters:     priv                MTX private file data
 *                 cmd                 ioctl command
 *                 arg                 ioctl argument
 *                 
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       Allocation of shared buffers can only be done by a root process.
 */
int mtx_ioctl_get_buffer(mtx_file_t *priv, unsigned int cmd, unsigned long arg)
{
    mtx_ioctl_buffer_t buffer_info;
    mtx_device_t *dev = priv->dev;
    mtx_region_t *region = NULL;
    struct list_head *pos;

    if (!dev) return -EINVAL;

    if (copy_from_user(&buffer_info, (void*)arg, sizeof(buffer_info)))
        return -EFAULT;

    if ((buffer_info.type < 0) || (buffer_info.type >= MTX_MEMTYPE_COUNT) ||
        (buffer_info.scope < MTX_MEMSCOPE_PRIVATE) || (buffer_info.type >= MTX_MEMSCOPE_COUNT))
        return -EINVAL;

    if (buffer_info.scope >= MTX_MEMSCOPE_ROOT && buffer_info.createNewRegion==0) {
        
        /* Check if an existing device region match the requested one */
        list_for_each(pos, &dev->memory_regions) {
            
            mtx_region_t* tmp_region = list_entry(pos, mtx_region_t, list_node);

            if ((tmp_region) &&
                (tmp_region->scope == buffer_info.scope) &&
                (tmp_region->size == buffer_info.size)) {
            
                /* Check if we are authorized to access this region */
                if (((tmp_region->scope == MTX_MEMSCOPE_ROOT) && (!priv->admin)) ||
                    ((tmp_region->scope == MTX_MEMSCOPE_AUTH) && (!priv->auth)))
                    return -EPERM;

                region = tmp_region;
                break;
            }
        }
        
        /* Check if we can allocate such buffer (only root processes are allowed) */
        if (!region && !priv->admin)
            return -EPERM;
    }

    if (!region)
    {
        /* Allocate new context region */
        region = MtxRegion_Alloc(dev, buffer_info.size, buffer_info.type, buffer_info.scope, NULL);
        if (!region) {
            MTX_ERROR("Fail to allocate buffer of type:%u scope:%u size:%lu (by process %d)\n", 
                       buffer_info.type, buffer_info.scope, buffer_info.size, current->pid);
            return -ENOMEM;
        }

        region->scope = buffer_info.scope;
    }

    if (region->scope == MTX_MEMSCOPE_PRIVATE)
    {
        /* Set context id to region */
        region->pid = current->pid;
    }
    else
    {
        /* Increment region refcount */
        atomic_inc(&region->ref_count);
    }
    
    /* copy memory base and (real) size to user-space */
    buffer_info.base = region->base;
    buffer_info.size = region->size;

    if (copy_to_user((void*)arg, &buffer_info, sizeof(buffer_info)))
        return -EFAULT;
    
    return 0;
}

/***************************************************************************************
 * Function:       mtx_ioctl_release_buffer
 *
 * Description:    Release a buffer. If this buffer is private, it will be automatically
 *                 destroyed. If it is shared with the kernel or with other processes,
 *                 it will be destroyed only when no more process instance refers to it.
 *
 * Parameters:     priv                MTX private file data
 *                 cmd                 ioctl command
 *                 arg                 ioctl argument
 *                 
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       None.
 */
int mtx_ioctl_release_buffer(mtx_file_t *priv, unsigned int cmd, unsigned long arg)
{
    mtx_ioctl_buffer_t buffer_info;
    mtx_device_t *dev = priv->dev;
    mtx_region_t *region;

    if (!dev) return -EINVAL;

    if (copy_from_user(&buffer_info, (void*)arg, sizeof(buffer_info)))
        return -EFAULT;

    /* find region from base address */
    region = MtxRegion_FindFromBase(dev, buffer_info.base);

    if (!region ||
        (region->size != buffer_info.size) ||
        (region->type != buffer_info.type) ||
        (region->scope != buffer_info.scope) ||
        (region->scope < MTX_MEMSCOPE_PRIVATE))
    {
        MTX_ERROR("Releasing invalid buffer base:0x%08lx, size:%lu, type:%u, scope:%u (process: %d)\n",
                  buffer_info.base, buffer_info.size, buffer_info.type, buffer_info.scope, current->pid); 
        return -EINVAL;
    }

    if (((region->scope == MTX_MEMSCOPE_ROOT) && (!priv->admin)) ||
        ((region->scope == MTX_MEMSCOPE_AUTH) && (!priv->auth)))
        return -EPERM;
    
    MTX_DEBUG("Releasing buffer at 0x%08lx\n", region->base);

    if ((region->scope == MTX_MEMSCOPE_PRIVATE) ||
        (atomic_dec_and_test(&region->ref_count)))
    {
        /* private region or last reference to it, free memory */
        MtxRegion_Free(dev, region);
    }
    
    return 0;
}

/***************************************************************************************
 * Function:       mtx_ioctl_driver_value_request
 *
 * Description:    This ioctl can be used to either retrieved a specific 32 bits value 
 *                 in driver, or either change this value. 
 *                 
 * Parameters:     priv                MTX private file data
 *                 cmd                 ioctl command
 *                 arg                 ioctl argument
 *                 
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       
 */
int mtx_ioctl_driver_value_request(mtx_file_t *priv, unsigned int cmd, unsigned long arg)
{
    mtx_ioctl_value_t req_info;
    char symname[MTX_MODULE_PARM_MAX_LENGTH] = MTX_MODULE_PARM_PREFIX;
    void* symval;

    if (copy_from_user(&req_info, (void*)arg, sizeof(req_info)))
        return -EFAULT;

    switch (req_info.type) {

        case MTX_REQUEST_DRIVER_PARM_WRITE:

            // Parameters write are reserved to root applications
            if (!priv->admin)
                return -EPERM;

            strncat(symname, req_info.parm_name, MTX_MODULE_PARM_MAX_LENGTH - strlen(symname) - 1);
            symname[MTX_MODULE_PARM_MAX_LENGTH - 1] = '\0';

            symval = mtx_find_symbol(symname);
            if (!symval) return -EINVAL;

            *(unsigned long*)symval = req_info.value;
            break;
            
        case MTX_REQUEST_DRIVER_PARM_READ:

            strncat(symname, req_info.parm_name, MTX_MODULE_PARM_MAX_LENGTH - strlen(symname) - 1);
            symname[MTX_MODULE_PARM_MAX_LENGTH - 1] = '\0';

            symval = mtx_find_symbol(symname);
            if (!symval) return -EINVAL;

            req_info.value = *(unsigned long*)symval;
            break;

        default:
            return -EINVAL;
    }

    if (copy_to_user((void*)arg, &req_info, sizeof(req_info)))
        return -EFAULT;

    return 0;
}

/********************** FOR DEBUGGING PURPOSE ONLY ***************************
 * 
 * Function:       mtx_ioctl_hard_reset
 *
 * Description:    Do a hard reset of the module use count, so we can unload
 *                 the module if something bad happened.
 *
 * Parameters:     priv                MTX private file data
 *                 cmd                 ioctl command
 *                 arg                 ioctl argument
 *                 
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       
 */
int mtx_ioctl_hard_reset(mtx_file_t *priv, unsigned int cmd, unsigned long arg)
{
#if DEBUG
    mtx_device_t *dev = priv->dev;
    
#ifdef KERNEL_2_4
    MTX_DEBUG("DOING HARD RESET! (current_mod_use = %u)\n", MOD_IN_USE);

    while (MOD_IN_USE > 1) 
        MOD_DEC_USE_COUNT; 
#endif

    if (dev) mtx_dev_cleanup(dev);
#endif
    return 0;
}
