#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/mm.h>
#include <asm/mman.h>
#include <linux/sched.h> /*current*/
#include <asm/io.h>
#include <linux/pci.h>
#include <linux/spinlock.h>
#include <asm/pgtable.h>
#include <asm/system.h>

#include "xvif.h"
#include "vixsdma.h"
#include "xvethioctl.h"
#include "xvdrv_if.h"

#define XVDEV_IOCTL_SEND_TIMEOUT  (10*HZ)

#define VETH_SHARP_MOD 1 /* for SHARP Co. */

static struct ioctl_param xvdev_ioctl_param[XVDEV_MAX_HANDLE];


static void xvdev_release_handle(int handle)
{
    int i;

	if(xvdev_ioctl_param[handle].send_buf_user)
    {
        do_munmap( current->mm,(uint32) xvdev_ioctl_param[handle].send_buf_user, 
            xvdev_ioctl_param[handle].max_buf_size);
        xvdev_ioctl_param[handle].send_buf_user= 0;
    }

    if(xvdev_ioctl_param[handle].send_buf)
    {
        free_pages((unsigned long)(xvdev_ioctl_param[handle].send_buf),
			get_order(xvdev_ioctl_param[handle].max_buf_size));
        xvdev_ioctl_param[handle].send_buf = NULL;
    }


        
    for(i=0;i<RECV_BUF_NUM;i++)
    {
	    if(xvdev_ioctl_param[handle].recv_buf_user[i])
        {
            do_munmap( current->mm,(uint32) xvdev_ioctl_param[handle].recv_buf_user[i], 
                xvdev_ioctl_param[handle].max_buf_size);
            xvdev_ioctl_param[handle].recv_buf_user[i] = 0;
        }

        if(xvdev_ioctl_param[handle].recv_buf[i])
        {
            free_pages((unsigned long)(xvdev_ioctl_param[handle].recv_buf[i]),
				get_order(xvdev_ioctl_param[handle].max_buf_size));
            xvdev_ioctl_param[handle].recv_buf[i] = NULL;
        }

     
    }

    xvdev_ioctl_param[handle].flag = NULL_HANDLE;
    
}

static unsigned int map_kernel_to_user(unsigned int kaddr,unsigned size)
{
    struct vm_area_struct *vma;
    unsigned int useraddr;
    int ret;
    
    down_write((struct rw_semaphore *)&current->mm->mmap_sem);
    
    useraddr = do_mmap(NULL,0,size,
        PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS,0);
    
    vma = find_vma(current->mm,useraddr);
    if(NULL == vma)
    {
        useraddr = 0;
        goto map_user_exit;
    }

    PDEBUG("vma->vm_start:0x%x,vma->vm_end:0x%x\n",vma->vm_start,vma->vm_end);

        
    ret = remap_pfn_range(vma,
        vma->vm_start,
        virt_to_phys((void *)kaddr) >> PAGE_SHIFT,
        vma->vm_end - vma->vm_start,
    #ifdef XC3ARC_BUILD
        PAGE_SHARED_NO_CACHE);
    #elif defined(PPC_BUILD)
        PAGE_SHARED);
    #else
    //PAGE_SHARED);
    //vma->vm_page_prot);
#if VETH_SHARP_MOD
	__pgprot(_PAGE_PRESENT | __READABLE | __WRITEABLE|_CACHE_UNCACHED)); /* for MIPS */
#else
    __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_USER | _PAGE_ACCESSED | _PAGE_NX |_PAGE_PCD));
#endif
    #endif

    if(ret<0)
            useraddr = 0;

map_user_exit:

    up_write((struct rw_semaphore *)&current->mm->mmap_sem);
    return useraddr;

}


int xvdev_allocate_kernel_mem(PVETHDEV pVdev,void *parg)
{
    struct para_alloc_kernel_mem *preq = (struct para_alloc_kernel_mem *)parg;
    
    preq->memaddr_kernel  = (unsigned int)__get_free_pages(GFP_KERNEL,
    	get_order(preq->memsize));
    if(!preq->memaddr_kernel)
    {
        preq->return_value = VDEV_IOCTL_NO_MEM;    
        return -ENOMEM;
    }

    preq->memaddr_bus = pci_map_single(pVdev->ppcidev,
                                (void *)preq->memaddr_kernel,
                                preq->memsize,
                                PCI_DMA_FROMDEVICE);

    
    preq->memaddr_user = 
        map_kernel_to_user((unsigned int)preq->memaddr_kernel,preq->memsize);
    if(!preq->memaddr_user)
    {
        free_pages((unsigned long)preq->memaddr_kernel,
			get_order(preq->memsize));
        preq->memaddr_kernel = 0;
        
        preq->return_value = VDEV_IOCTL_NO_MEM;    
        return -ENOMEM;
    }

    
    PDEBUG("Allocated Kernel Mem vAddr %x pAddr %x size %x\n", preq->memaddr_user, preq->memaddr_kernel, preq->memsize);

    
    return 0;
    
}


int xvdev_release_kernel_mem(PVETHDEV pVdev,void *parg)
{
    struct para_release_kernel_mem *preq = (struct para_release_kernel_mem *)parg;    

    PDEBUG("Release Kernel Mem vAddr %x pAddr %x size %x\n", preq->memaddr_user, preq->memaddr_kernel, preq->memsize);
    
    if(preq->memaddr_user)
    {
        do_munmap( current->mm,(uint32) preq->memaddr_user, 
            preq->memsize);
    }

    if(preq->memaddr_kernel)
    {
        free_pages((unsigned long)preq->memaddr_kernel,
			get_order(preq->memsize));
    }

    return 0;
}

int xvdev_get_rxbuf(unsigned int handle,unsigned recv_size,void **rxbuf)
{
    struct ioctl_param *pparam;
    //void *p;
    
    pparam = &xvdev_ioctl_param[handle];

    if(pparam->flag!= HANDLE_OPENED)
        return VDEV_IOCTL_PEER_NOT_OPENED;

    //if(pparam->recv_full) 
    if(((pparam->recv_head+1) & RECV_BUF_MASK) == pparam->recv_tail)
        return VDEV_IOCTL_PEER_BUF_FULL;

    *rxbuf = pparam->recv_buf[pparam->recv_head];
    pparam->recv_buf_size[pparam->recv_head]=recv_size;

    pparam->recv_head++;
    pparam->recv_head &= RECV_BUF_MASK;

    //if(pparam->recv_head == pparam->recv_tail)
        //pparam->recv_full=1;

    return 0;
}

void xvdev_recv_notify(unsigned int handle)
{
    PDEBUG("--->%s entry.\n",__FUNCTION__);
    xvdev_ioctl_param[handle].recv_flag = 1;
    wake_up_interruptible(&xvdev_ioctl_param[handle].recv_queue);
}

void xvdev_ioctl_init()
{
    PDEBUG("--->%s entry.\n",__FUNCTION__);
    memset(xvdev_ioctl_param,0x0,sizeof(xvdev_ioctl_param));

}

void xvdev_ioctl_release()
{
    int i;
    PDEBUG("--->%s entry.\n",__FUNCTION__);
    for(i=0;i<XVDEV_MAX_HANDLE;i++)
    {
        if(xvdev_ioctl_param[i].flag != NULL_HANDLE);
            xvdev_release_handle(i);
    }
}

int xvdev_cmd_open(PVDEV pVdev,void *parg)
{
    int i;
    //int res;
    struct para_open_req  * preq = (struct para_open_req *)parg;
    int handle;

    PDEBUG("--->%s entry.\n",__FUNCTION__);
    
    handle = preq->handle;
    if((handle < 0) || (handle >= XVDEV_MAX_HANDLE))
        return -EINVAL;
    
    if(NULL_HANDLE != xvdev_ioctl_param[handle].flag)
        return -EINVAL;

#ifndef VIXS_VCOMM_DRV
		
	xvdev_ioctl_param[handle].max_buf_size = VIXS_DMA_MAX_TRANSFER_SIZE;
	
#else

	if(preq->input_maxsize> 0x1000000)
	{
		printk("buf size must be less than 0x1000000\n");
		return -EINVAL;
	}

	if(0 == preq->input_maxsize)
	{
		xvdev_ioctl_param[handle].max_buf_size = VIXS_DMA_MAX_TRANSFER_SIZE;
	}
	else
	{
		xvdev_ioctl_param[handle].max_buf_size=preq->input_maxsize;
	}

#endif
	//printk(KERN_CRIT "VETH: order:0x%x\n" , get_order(xvdev_ioctl_param[handle].max_buf_size));

    xvdev_ioctl_param[handle].send_buf = (void *)__get_free_pages(GFP_KERNEL,
    	get_order(xvdev_ioctl_param[handle].max_buf_size));
    if(NULL == xvdev_ioctl_param[handle].send_buf)
        goto  ioctl_open_exit;
    
    xvdev_ioctl_param[handle].send_buf_user = 
        map_kernel_to_user((unsigned int)xvdev_ioctl_param[handle].send_buf,xvdev_ioctl_param[handle].max_buf_size);
    if(!xvdev_ioctl_param[handle].send_buf_user)
        goto ioctl_open_exit; 

    PDEBUG("Send buf user space address:0x%x.\n", xvdev_ioctl_param[handle].send_buf_user);

    for(i=0;i<RECV_BUF_NUM;i++)
    {
        xvdev_ioctl_param[handle].recv_buf[i] = (void *)__get_free_pages(GFP_KERNEL,
			get_order(xvdev_ioctl_param[handle].max_buf_size));
        if(NULL == xvdev_ioctl_param[handle].recv_buf[i])
            goto ioctl_open_exit;

        xvdev_ioctl_param[handle].recv_buf_user[i] = 
            map_kernel_to_user((unsigned int)xvdev_ioctl_param[handle].recv_buf[i],xvdev_ioctl_param[handle].max_buf_size);
        if(!xvdev_ioctl_param[handle].recv_buf_user[i])
            goto ioctl_open_exit; 

        PDEBUG("Recv buf user space address:0x%x.\n", 
            xvdev_ioctl_param[handle].recv_buf_user[i]);

    }
    
    preq->handle = handle;
    preq->input_buf = xvdev_ioctl_param[handle].send_buf_user;
    preq->input_buf_phy = (unsigned int)xvdev_ioctl_param[handle].send_buf;
    preq->input_maxsize = xvdev_ioctl_param[handle].max_buf_size ;

    xvdev_ioctl_param[handle].flag = HANDLE_OPENED;
    xvdev_ioctl_param[handle].handle = handle;
    xvdev_ioctl_param[handle].recv_head = 0;
    xvdev_ioctl_param[handle].recv_tail = 0;
    xvdev_ioctl_param[handle].recv_cur_addr = 0;
    
    init_waitqueue_head(&xvdev_ioctl_param[handle].send_queue);
    init_waitqueue_head(&xvdev_ioctl_param[handle].recv_queue);
    spin_lock_init(&xvdev_ioctl_param[handle].recv_lock);

    return 0;
    
ioctl_open_exit:


    xvdev_release_handle(handle);

    return -ENOMEM;

}

int xvdev_cmd_close(PVDEV pVdev,void *parg)
{
    struct para_close_req  * preq = (struct para_close_req *)parg;
    PDEBUG("--->%s entry.\n",__FUNCTION__);

    if((preq->handle < 0) || (preq->handle >= XVDEV_MAX_HANDLE))
        return -EINVAL;

    if(HANDLE_OPENED == xvdev_ioctl_param[preq->handle].flag)
        xvdev_release_handle(preq->handle);
    
    return 0;
}

int xvdev_cmd_send(PVDEV pVdev, void *parg)
{
    struct para_send_req  * preq = (struct para_send_req *)parg;
    VdevMsg vdevmsg;
    int handle;
    //int count = 50;
    int res;
    unsigned long flags;
        
    PDEBUG("--->%s entry.\n",__FUNCTION__);

    if(MMR_READ(pVdev->proc_comm.peer_flag) != 0xdeadbeef)
    {
        preq->return_value = VDEV_IOCTL_PEER_NOT_OPENED;
        return -EINVAL;
    }

    handle = preq->handle;
    if((handle < 0) || (handle >= XVDEV_MAX_HANDLE))
    {
        preq->return_value = VDEV_IOCTL_UNCORRECT_PARAMETER;
        return -EINVAL;
    }

    if(HANDLE_OPENED != xvdev_ioctl_param[handle].flag)
    {
        
        preq->return_value = VDEV_IOCTL_UNCORRECT_PARAMETER;
        return -EINVAL;
    }

    PDEBUG("Input buffer size:%d.\n",preq->input_size);
    if((preq->input_size == 0) || (preq->input_size > xvdev_ioctl_param[handle].max_buf_size))
    {
        preq->return_value = VDEV_IOCTL_UNCORRECT_PARAMETER;
        return -EINVAL;
    }

    
    vdevmsg.cmd = CMD_IOCTL;
    vdevmsg.buff_phy = pci_map_single(pVdev->ppcidev,
                                (void *)xvdev_ioctl_param[handle].send_buf,
                                preq->input_size,
                                PCI_DMA_TODEVICE);
    vdevmsg.buff_size = preq->input_size;
    vdevmsg.msg_data = (unsigned int)(&xvdev_ioctl_param[handle]);
    vdevmsg.dev_id = preq->dev_id;

    /*
    do
    {
        res = xveth_sendmsg(pVdev,&vdevmsg);
    }while((res !=0) && ((--count)>0));
    */

    local_irq_save(flags);
    res = xvdev_sendmsg(pVdev,&vdevmsg);
    xvdev_ioctl_param[handle].send_flag = 0;
    local_irq_restore(flags);
    if(res!=0)
    {

        printk(KERN_ALERT"Message queue is full.\n");
        
#ifndef XC3ARC_BUILD
        pci_unmap_single(pVdev->ppcidev,
                    (void *)(vdevmsg.buff_phy),
                    vdevmsg.buff_size,
                    PCI_DMA_TODEVICE);
#endif
        preq->return_value = VDEV_IOCTL_MSG_FULL;
        return -EINVAL;
    }

    res = wait_event_interruptible_timeout(xvdev_ioctl_param[handle].send_queue,
        xvdev_ioctl_param[handle].send_flag,XVDEV_IOCTL_SEND_TIMEOUT);


#ifndef XC3ARC_BUILD
    pci_unmap_single(pVdev->ppcidev,
            (void *)(vdevmsg.buff_phy),
            vdevmsg.buff_size,
            PCI_DMA_TODEVICE);
#endif

    PDEBUG("After wait jiffers:0x%x,HZ:%d\n",jiffies,HZ);
    PDEBUG("res:%d\n",res);

    PDEBUG("send_return:%d\n",xvdev_ioctl_param[handle].send_return);

    if(1==xvdev_ioctl_param[handle].send_flag)
    {
        if(VDEV_CMD_ACK_OK == xvdev_ioctl_param[handle].send_return)
        {
            PDEBUG("--->%s is successful.\n",__FUNCTION__);
            return 0;
        }
        else
        {
            preq->return_value = xvdev_ioctl_param[handle].send_return;
            return -EINVAL;
        }
    }
    else
    {
        PDEBUG("######res:0x%x",res);
    }
    preq->return_value = VDEV_IOCTL_SEND_TIMEOUT;

    return -EINVAL;
}

int xvdev_cmd_recv(PVDEV pVdev,void *parg)
{

    struct para_recv_req  * preq = (struct para_recv_req *)parg;
    int handle;
    unsigned long flag;
    struct ioctl_param *pparam;
    int timeout;
    
    //PDEBUG("--->%s entry.\n",__FUNCTION__);

    handle = preq->handle;
    if((handle < 0) || (handle >= XVDEV_MAX_HANDLE))
        return -EINVAL;

    pparam = &xvdev_ioctl_param[handle];
    
    if(HANDLE_OPENED != pparam->flag)
        return -EINVAL;

    spin_lock_irqsave(&pparam->recv_lock,flag);

    if(preq->output_buf != 0)
    {
        pparam->recv_tail++;
        pparam->recv_tail &= RECV_BUF_MASK;
        pparam->recv_cur_addr = 0;
        preq->output_buf = 0;
    }	

    //if(!((pparam->recv_tail == pparam->recv_head)&& (0==pparam->recv_full)))
    if(pparam->recv_tail != pparam->recv_head)
    {
    
        preq->output_buf = pparam->recv_buf_user[pparam->recv_tail];
        preq->output_size = pparam->recv_buf_size[pparam->recv_tail];
        preq->output_buf_phy = (unsigned int)pparam->recv_buf[pparam->recv_tail];
            
        PDEBUG("Immediately recv, buffer index:0x%x\n",pparam->recv_tail);
        PDEBUG("Immediately recv, buffer output buf:0x%x\n",preq->output_buf);
        PDEBUG("Immediately recv, buffer buf_size:0x%x\n",preq->output_size);
    
        //pparam->recv_tail++;
        //pparam->recv_tail &= RECV_BUF_MASK;
        //pparam->recv_full = 0;

        pparam->recv_cur_addr = preq->output_buf;
        spin_unlock_irqrestore(&pparam->recv_lock,flag);
        return 0;
    }
    else
    {
        pparam->recv_flag = 0;		
    }

    spin_unlock_irqrestore(&pparam->recv_lock,flag);

    timeout = preq->timeout;
    //if(timeout > XVETH_MAX_RECV_TIMEOUT)
        //timeout = XVETH_MAX_RECV_TIMEOUT;

    //PDEBUG("Recv timeout:0x%x\n",timeout);
    wait_event_interruptible_timeout(pparam->recv_queue,pparam->recv_flag,timeout*HZ/1000);

    spin_lock_irqsave(&pparam->recv_lock,flag);
    //if(!((pparam->recv_tail == pparam->recv_head)&& (0==pparam->recv_full)))
    if(pparam->recv_tail != pparam->recv_head)
    {
    
        preq->output_buf = pparam->recv_buf_user[pparam->recv_tail];
        preq->output_size = pparam->recv_buf_size[pparam->recv_tail];
        preq->output_buf_phy = (unsigned int)pparam->recv_buf[pparam->recv_tail];
        
        PDEBUG("Delayed recv, buffer index:0x%x\n",pparam->recv_tail);
        PDEBUG("Delayed recv, buffer output buf:0x%x\n",preq->output_buf);
        PDEBUG("Delayed recv, buffer buf_size:0x%x\n",preq->output_size);
    
        //pparam->recv_tail++;
        //pparam->recv_tail &= RECV_BUF_MASK;
        //pparam->recv_full = 0;
        pparam->recv_cur_addr = preq->output_buf;
        spin_unlock_irqrestore(&pparam->recv_lock,flag);
        return 0;
    }

    PDEBUG("Can't receive correctly.\n");
    preq->return_value = VDEV_IOCTL_RECV_TIMEOUT;
    spin_unlock_irqrestore(&pparam->recv_lock,flag);
    return -EINVAL;
}

