/*
 * read_write.c  -- MATRIX VISION Frame Grabber Driver for mvHyperion series.
 *
 *   Copyright (c) 2006 MATRIX VISION GmbH (info@matrix-vision.de)
 *
 $Id: read_write.c,v 1.16 2010-06-23 14:52:23 ug Exp $
 *
 */
/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "drivermain.h"
#include "hyperion_base.h"

//-------------------------------------------------------------------------------------------
void complete_request( void* io_obj, unsigned char status )
//-------------------------------------------------------------------------------------------
{
    struct io_object* ioobj = ( struct io_object* )io_obj;
    if( ioobj )
    {
        //PRINTKM(DMA,(PKTD "%s ioobj %p\n", ioobj->device_index, __FUNCTION__, ioobj ));
        hyperion_complete( ioobj );
        aio_complete( ioobj->iocb, ioobj->count, 0 );
    }
}

/*
//-------------------------------------------------------------------------------------------
static int cancel_async_read_write( struct kiocb *iocb, struct io_event *event )
//-------------------------------------------------------------------------------------------
{
    struct io_object *ioobj = (struct io_object *)iocb->private;
    struct dma_transfer_object *dto = (struct dma_transfer_object *)ioobj->dma_transfer_object;
    long result = 0, user_result = 0;

    //if( iocb->privatedata->transfering )
    //  stop tranfer
    cancel_iocb( dto->rw_queue, iocb );
    aio_complete( iocb, result, user_result );
    return 0;
}
*/

//-------------------------------------------------------------------------------------------
int sgl_get_max_pages( UINT_PTR uaddr, size_t count )
//-------------------------------------------------------------------------------------------
{
    UINT_PTR end = ( uaddr + count + PAGE_SIZE - 1 ) >> PAGE_SHIFT;
    UINT_PTR start = uaddr >> PAGE_SHIFT;
    const int max_pages = ( int )( end - start );
    PRINTKM( MEM, ( PKTD "%s(uaddr 0x%lx count 0x%zx): max_pages 0x%ux\n", -1, __FUNCTION__, uaddr, count, max_pages ) );
    return max_pages;
}

//-------------------------------------------------------------------------------------------
// The following functions may be useful for a larger audience.
// copied from streaming tape device driver st.c
int sgl_map_user_pages( struct scatterlist* sgl, const unsigned int max_pages, UINT_PTR uaddr, size_t count, int rw )
//-------------------------------------------------------------------------------------------
{
    const int nr_pages = sgl_get_max_pages( uaddr, count );
    int res, i, j;
    struct page** pages;

    PRINTKM( MEM, ( PKTD "sgl_map_user_pages() sgl p%p max_pages 0x%ux uaddr 0x%lx count 0x%zx nr_pages 0x%x\n", -1, sgl, max_pages, uaddr, count, nr_pages ) );
    /* User attempted Overflow! */
    if ( ( uaddr + count ) < uaddr )
    {
        PRINTKM( FILE, ( PKTD "sgl_map_user_pages() (uaddr + count) < uaddr \n", -1 ) );
        return -EINVAL;
    }

    /* Too big */
    if ( nr_pages > max_pages )
    {
        PRINTKM( FILE, ( PKTD "sgl_map_user_pages() nr_pages > max_pages\n", -1 ) );
        return -ENOMEM;
    }

    /* Hmm? */
    if ( count == 0 )
    {
        PRINTKM( FILE, ( PKTD "sgl_map_user_pages() count == 0\n", -1 ) );
        return 0;
    }

    if ( ( pages = vmalloc( max_pages * sizeof( *pages ) ) ) == NULL )
    {
        PRINTKM( FILE, ( PKTD "pages == NULL vmalloc returns NULL ( size 0x%zx )\n", -1, ( max_pages * sizeof( *pages ) ) ) );
        return -ENOMEM;
    }

    /* Try to fault in all of the necessary pages */
    //PRINTKM(MEM,(PKTD "sgl_map_user_pages() down_read(&current->mm->mmap_sem)\n", -1 ));
    down_read( &current->mm->mmap_sem );
    /* rw==READ means read from drive, write into memory area */
    res = get_user_pages( current, current->mm, uaddr, nr_pages, rw == READ, 0, pages, NULL );
    up_read( &current->mm->mmap_sem );
    //PRINTKM(MEM,(PKTD "get_user_pages() returns %d nr_pages %d \n", -1, res, nr_pages));

    /* Errors and no page mapped should return here */
    if ( res < nr_pages )
    {
        goto out_unmap;
    }

    for ( i = 0; i < nr_pages; i++ )
    {
        //FIXME: flush superflous for rw==READ, probably wrong function for rw==WRITE
        flush_dcache_page( pages[i] );
    }

    /* Populate the scatter/gather list */
#if LINUX_VERSION_CODE >= KERNEL_VERSION( 2, 6, 24 )
    sg_init_table( sgl, nr_pages );
    sg_set_page( &sgl[0], pages[0], 0, ( uaddr & ~PAGE_MASK ) );
    sgl[0].length = PAGE_SIZE - sgl[0].offset;
#else
    sgl[0].page = pages[0];
    sgl[0].offset = uaddr & ~PAGE_MASK;
    sgl[0].dma_address = 0;
    sgl[0].length = PAGE_SIZE - sgl[0].offset;
#endif
    /*
    #if !defined(CONFIG_HIGHMEM64G) && (BITS_PER_LONG == 32)
        PRINTKM(MEM,(PKTD "sgl_map_user_pages() page %p __pa 0x%x off 0x%x len 0x%x\n", -1, sg_page( &sgl[0] ), sgl[0].dma_address, sgl[0].offset, sgl[0].length));
    #else
        PRINTKM(MEM,(PKTD "sgl_map_user_pages() page %p __pa 0x%llx off 0x%x len 0x%x\n", -1, sg_page( &sgl[0] ), sgl[0].dma_address, sgl[0].offset, sgl[0].length));
    #endif
    */
    if ( nr_pages > 1 )
    {
        sgl[0].length = PAGE_SIZE - sgl[0].offset;
        count -= sgl[0].length;
        for ( i = 1; i < nr_pages ; i++ )
        {
#if LINUX_VERSION_CODE >= KERNEL_VERSION( 2, 6, 24 )
            sg_set_page( &sgl[i], pages[i], ( count < PAGE_SIZE ? count : PAGE_SIZE ), 0 );
#else
            sgl[i].offset = 0;
            sgl[i].page = pages[i];
            sgl[i].length = count < PAGE_SIZE ? count : PAGE_SIZE;
            sgl[i].dma_address = 0;
#endif
            count -= PAGE_SIZE;

            /*
            #if !defined(CONFIG_HIGHMEM64G) && (BITS_PER_LONG == 32)
                PRINTKM(MEM,(PKTD "sgl_map_user_pages() page[%d] %p __pa 0x%x off 0x%x len 0x%x\n", -1, i, sg_page( &sgl[i] ), sgl[i].dma_address, sgl[i].offset, sgl[i].length ));
            #else
                PRINTKM(MEM,(PKTD "sgl_map_user_pages() page[%d] %p __pa 0x%llx off 0x%x len 0x%x\n", -1, i, sg_page( &sgl[i] ), sgl[i].dma_address, sgl[i].offset, sgl[i].length ));
            #endif
            */
        }
    }
    else
    {
        sgl[0].length = count;
    }

    vfree( pages );
    return nr_pages;

out_unmap:
    PRINTKM( FILE, ( PKTD "error: get_user_pages() returns %d nr_pages %d\n", -1, res, nr_pages ) );
    if ( res > 0 )
    {
        for ( j = 0; j < res; j++ )
        {
            page_cache_release( pages[j] );
        }
        res = 0;
    }
    vfree( pages );
    return res;
}

//-------------------------------------------------------------------------------------------
// And unmap them...
// copied from streaming tape device driver st.c
// TODO remove function calls from tasklet routine because of kunmap and call them from IOCTL_READRESULTPACKET
int sgl_unmap_user_pages( struct scatterlist* sgl, const unsigned int nr_pages, int dirtied )
//-------------------------------------------------------------------------------------------
{
    int i;
    struct page* page;

    for ( i = 0; i < nr_pages; i++ )
    {
#if LINUX_VERSION_CODE >= KERNEL_VERSION( 2, 6, 24 )
        page = sg_page( &sgl[i] );
#else
        page = sgl[i].page;
#endif
        //PRINTKM(MEM,(PKTD "sgl_unmap_user_pages() page[%d] %p, setdirty %d\n", -1, i, page, !PageReserved(page)));
        if( ! PageReserved( page ) )
        {
            SetPageDirty( page );
        }
        /* FIXME: cache flush missing for rw==READ
         * FIXME: call the correct reference counting function
         */
        page_cache_release( page );
    }
    return 0;
}

//-------------------------------------------------------------------------------------------
static int setup_buffering_generic( struct kiocb* iocb, char __user* buf, size_t count, int is_read, struct user_buffer_list* ubuf_obj )
//-------------------------------------------------------------------------------------------
{
    struct hyperion_object* device = ( struct hyperion_object* )iocb->ki_filp->private_data;
    int nr_pages;
    unsigned short use_sg;

    PRINTKM( MEM, ( PKTD " >%s\n", device->index, __FUNCTION__ ) );
    if( ubuf_obj != NULL )
    {
        memset( ubuf_obj, 0, sizeof( struct user_buffer_list ) );
        use_sg = sgl_get_max_pages( ( UINT_PTR )buf, count );
        ubuf_obj->sg = vmalloc( use_sg * sizeof( struct scatterlist ) );
        if( ubuf_obj->sg == NULL )
        {
            PRINTKM( MEM, ( PKTD " %s vmalloc for sglist failed use_sg %d sizeof sg %lx\n", device->index, __FUNCTION__, use_sg, ( long unsigned int )sizeof( struct scatterlist ) ) );
            return -ENOMEM;
        }
        ubuf_obj->buf = buf;
        ubuf_obj->count = count;
        PRINTKM( MEM, ( PKTD " >%s, now fill sg list: sg list elements %d, buf %p, size %zu\n", device->index, __FUNCTION__, use_sg, ( void* )buf, count ) );
        nr_pages = sgl_map_user_pages( ubuf_obj->sg, use_sg, ( UINT_PTR )buf, count, ( is_read ? READ : WRITE ) );
        PRINTKM( MEM, ( PKTD " %s sgl_map_user_pages > result %d ioobj->sg p %p\n", device->index, __FUNCTION__, nr_pages, ubuf_obj->sg ) );
        if( nr_pages > 0 )
        {
            int dma_map_result = nr_pages, map_result;
#if LINUX_VERSION_CODE >= KERNEL_VERSION( 2, 6, 26 )
            map_result = dma_map_sg_attrs( &device->pdev->dev, ubuf_obj->sg, nr_pages, PCI_DMA_FROMDEVICE, ( struct dma_attrs* )device->pdma_attrs );
#else //KERNEL_VERSION( 2, 6, 26 )
            map_result = dma_map_sg( &device->pdev->dev, ubuf_obj->sg, nr_pages, PCI_DMA_FROMDEVICE );
#endif
            if( map_result <= 0 )
            {
                printk( " %s %d dma_map_sg() failed res %d ubuf %p count %x\n", __FUNCTION__, __LINE__, map_result, buf, ( unsigned int )count );
                dma_map_result = -ENOMEM;
            }
            if( dma_map_result <= 0 )
            {
                printk( " %s %d dma_map_sg() failed result %d\n", __FUNCTION__, __LINE__, dma_map_result );
                goto error_out_setup_buffering_generic;
            }
            ubuf_obj->nr_pages = nr_pages;
            return nr_pages;
        }
        goto error_out_setup_buffering_generic;
    }
    else
    {
        return 0;
    }

error_out_setup_buffering_generic:
    vfree( ubuf_obj->sg );
    ubuf_obj->sg = NULL;
    return 0;
}

//-------------------------------------------------------------------------------------------
static int setup_buffering( struct kiocb* iocb, char __user* buf, size_t count, int is_read, struct user_buffer_list** ubuf_obj_result )
//-------------------------------------------------------------------------------------------
{
    struct hyperion_object* device = ( struct hyperion_object* )iocb->ki_filp->private_data;
    int nr_pages;
    struct user_buffer_list* ubuf_obj = NULL;
    unsigned long irqflags;

    PRINTKM( MEM, ( PKTD " >%s\n", device->index, __FUNCTION__ ) );
    *ubuf_obj_result = 0;
#ifndef REMOVE_REQUEST_BUFFER_MAPPING
    ubuf_obj = get_user_buffer_obj( device, buf, count );
#endif
    if( ubuf_obj == NULL )
    {
        ubuf_obj = vmalloc( sizeof( struct user_buffer_list ) );
        if( ubuf_obj == NULL )
        {
            return -ENOMEM;
        }
        memset( ubuf_obj, 0, sizeof( struct user_buffer_list ) );
        nr_pages = setup_buffering_generic( iocb, buf, count, is_read, ubuf_obj );
        if( nr_pages > 0 )
        {
            ubuf_obj->nr_pages = nr_pages;
            spin_lock_irqsave( &device->s_lock, irqflags );
            list_add_tail( &ubuf_obj->list, &device->head_uaddr );
            spin_unlock_irqrestore( &device->s_lock, irqflags );
            *ubuf_obj_result = ubuf_obj;
            return nr_pages;
        }
        vfree( ubuf_obj );
        return 0;
    }
    *ubuf_obj_result = ubuf_obj;
    return ubuf_obj->nr_pages;
}

//-------------------------------------------------------------------------------------------
static ssize_t async_read_write( struct kiocb* iocb, char __user* user_buffer, size_t count, loff_t pos, int is_read )
//-------------------------------------------------------------------------------------------
{
    struct hyperion_object* device = ( struct hyperion_object* )iocb->ki_filp->private_data;
    struct io_object* ioobj = NULL;
    struct user_buffer_list* ubuf_obj = NULL;
    HyperionRequestBuffer request_buffer;
    int bytes_to_copy = 0;
    int result = -EINVAL;
    unsigned long copy_result = 0;

    PRINTKM( DMA, ( PKTD " %s( %lu ubuffer %p count %d is_read %d is_synch_io %d )\n", device->index, __FUNCTION__, jiffies, user_buffer, ( int )count, is_read, is_sync_kiocb( iocb ) ) );
    bytes_to_copy = count > sizeof( HyperionRequestBuffer ) ? sizeof( HyperionRequestBuffer ) : count;
    copy_from_user( ( void* )&request_buffer, ( void* )user_buffer, bytes_to_copy );
    PRINTKM( DMA, ( PKTD " leader=%p, size=%lu,  payload=%p, size=%llu,  trailer=%p, size=%lu\n", device->index,
                    ( void* )( ( UINT_PTR )request_buffer.leaderBufferPtr ), request_buffer.leaderBufferSize,
                    ( void* )( ( UINT_PTR )request_buffer.usermodePayloadBufferPtr ), request_buffer.usermodePayloadBufferSize,
                    ( void* )( ( UINT_PTR )request_buffer.trailerBufferPtr ), request_buffer.trailerBufferSize ) );
    PRINTKM( DMA, ( PKTD " %s copy_result = %lx\n", device->index, __FUNCTION__, copy_result ) );
    result = setup_buffering( iocb, ( char* )( ( UINT_PTR )request_buffer.usermodePayloadBufferPtr ), request_buffer.usermodePayloadBufferSize, is_read, &ubuf_obj );
    if( result <= 0 )
    {
        PRINTKM( FILE, ( PKTD "setup_buffering() failed result %d\n", device->index, result ) );
        result = -EFAULT;
        goto err_out;
    }
    if( ubuf_obj->private == NULL )
    {
        ioobj = kmalloc( sizeof( struct io_object ), GFP_KERNEL );
        if( ioobj == NULL )
        {
            printk( " %s error kmalloc try calloc for io_object\n", __FUNCTION__ );
            return -ENOMEM;
        }
        memset( ioobj, 0, sizeof( struct io_object ) );
        ubuf_obj->private = ( void* )ioobj;
    }
    else
    {
        ioobj = ( struct io_object* )ubuf_obj->private;
    }
    iocb->private = ( void* )ioobj;
    iocb->ki_cancel = NULL;
    ioobj->device = ( void* )device;
    ioobj->device_index = device->index;
    ioobj->iocb = iocb;
    ioobj->user_buffer = user_buffer;
    ioobj->count = count;
    ioobj->pos = pos;
    ioobj->ubuf_obj = ubuf_obj;
    ioobj->sg = ubuf_obj->sg;
    ioobj->do_dio = ubuf_obj->nr_pages;
    ioobj->start_jiffies = jiffies;
    memcpy( ( void* )&ioobj->request_buffer, ( void* )&request_buffer, sizeof( HyperionRequestBuffer ) );
    result = setup_buffering_generic( iocb, ( char* )( ( UINT_PTR )request_buffer.trailerBufferPtr ), request_buffer.trailerBufferSize, is_read, &ioobj->trailerbuf_obj );
    if( result <= 0 )
    {
        PRINTKM( FILE, ( PKTD "setup_buffering() failed result %d\n", device->index, result ) );
        result = -EFAULT;
        goto err_out;
    }

    result = hyperion_async_read( device, ioobj );
    if( result < 0 )
    {
        PRINTKM( FILE, ( PKTD "%s hyperion_read() failed result %d\n", device->index, __FUNCTION__, result ) );
        result = -EFAULT;
        goto err_out;
    }
    return -EIOCBQUEUED;

err_out:
    PRINTKM( FILE, ( PKTD "%s() failed result %d\n", device->index, __FUNCTION__, result ) );
    return result;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION (2,6,19)
//-------------------------------------------------------------------------------------------
ssize_t async_io_read( struct kiocb* iocb, char __user* buffer,  size_t count, loff_t pos )
//-------------------------------------------------------------------------------------------
{
    //printk( " %s \n", __FUNCTION__ );
    return async_read_write( iocb, buffer, count, pos, TRUE );
}

//-------------------------------------------------------------------------------------------
ssize_t async_io_write( struct kiocb* iocb, const char __user* buffer, size_t count, loff_t pos )
//-------------------------------------------------------------------------------------------
{
    return 0;
}
#else
//-------------------------------------------------------------------------------------------
ssize_t async_io_read( struct kiocb* iocb, const struct iovec* iocv, unsigned long count, loff_t pos )
//-------------------------------------------------------------------------------------------
{
//  printk( " %s iov_base p%p iov_len %d count %d pos %d\n", __FUNCTION__, iocv->iov_base, (int)iocv->iov_len, (int)count, pos );
    return async_read_write( iocb, ( char __user* )iocv->iov_base, iocv->iov_len, pos, TRUE );
}

//-------------------------------------------------------------------------------------------
ssize_t async_io_write( struct kiocb* iocb, const struct iovec* iocv, unsigned long count, loff_t pos )
//-------------------------------------------------------------------------------------------
{
    return 0;
}
#endif

//-------------------------------------------------------------------------------------------
unsigned int hyperion_poll( struct file* filp, struct poll_table_struct* wait )
//-------------------------------------------------------------------------------------------
//usr/include/X11/Xtrans/Xtrans.h;
{
    //struct hyperion_object *device = filp->private_data;
    unsigned int mask = 0;

    //  int left = (dev->rp + dev->buffersize - dev->wp) % dev->buffersize;

    //poll_wait( filp, &device->result_queue, wait );
    //if (dev->rp != dev->wp) mask |= POLLIN | POLLRDNORM; /* lesbar */
    //if (left != 1)     mask |= POLLOUT | POLLWRNORM; /* schreibbar */

    //mask = !is_result_queue_empty( &device->result_queue );
    return mask;
}

