#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <linux/mman.h>

#include <asm/io.h>
#include <asm/page.h>
#include <asm/uaccess.h>
#include <linux/version.h>

MODULE_AUTHOR("Andrea Gasparini");
MODULE_DESCRIPTION("Linux Kernel - MMap Test module");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL");


#define DEVNAME "mmap_test"

unsigned int major = 0;
static unsigned long maxframebuffer = PAGE_SIZE;
static char* buffer = NULL;

#define BUFFER_ADDRESS (0x80000000 + 200u*1024u*1024u)

void simple_vma_open(struct vm_area_struct *vma){
    printk("VMA open: vma->vm_start: %lx - vma->pgoffs: %lx - vma->vm_end: %lx, vma_flags: %lx \n",
            vma->vm_start, vma->vm_pgoff, vma->vm_end,vma->vm_flags);
}

int simple_vma_fault(struct vm_area_struct* vma, struct vm_fault *vmf){
    printk("VMA FAULT: vma->vm_start: %lx - vma->pgoffs: %lx - vma->vm_end: %lx, vma_flags: %lx \n",
            vma->vm_start, vma->vm_pgoff, vma->vm_end,vma->vm_flags);
}

int simple_vma_mkrite(struct vm_area_struct* vma, struct vm_fault *vmf){
    printk("VMA MKWRITE: vma->vm_start: %lx - vma->pgoffs: %lx - vma->vm_end: %lx, vma_flags: %lx \n",
            vma->vm_start, vma->vm_pgoff, vma->vm_end,vma->vm_flags);
}

void simple_vma_close(struct vm_area_struct *vma){
    printk("VMA close: vma->vm_start: %lx - vma->pgoffs: %lx - vma->vm_end: %lx, vma_flags: %lx \n",
            vma->vm_start, vma->vm_pgoff, vma->vm_end,vma->vm_flags);
}

static struct vm_operations_struct simple_remap_vm_ops = {
    .open = simple_vma_open,
    .fault = simple_vma_fault,
    .page_mkwrite = simple_vma_mkrite,
    .close = simple_vma_close,

};

int mmaptest_mmap(struct file *filp, struct vm_area_struct *vma)
{
    unsigned int buffersize = 0;

    /* Total length of buffers */
    buffersize = (maxframebuffer >> PAGE_SHIFT)+1;

/*    printk("mmap before buffersize check buffersize: %ld requested: %ld\n",buffersize,(vma->vm_end - vma->vm_start));*/
    if ( (vma->vm_end - vma->vm_start)>>PAGE_SHIFT > buffersize )
        return -ENOMEM;

    vma->vm_flags |= VM_IO | VM_RESERVED;
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

    vma->vm_pgoff = virt_to_phys(buffer) >> PAGE_SHIFT;
    //vma->vm_pgoff = BUFFER_ADDRESS >> PAGE_SHIFT;
    if (remap_pfn_range(vma, vma->vm_start,
                    virt_to_phys(buffer) >> PAGE_SHIFT,
                    vma->vm_end - vma->vm_start,
                    vma->vm_page_prot)){
        printk("remap returned something wrong\n");
        return -EAGAIN;
    }


    printk("mmap worked. vmaflasg: %lx phys: %p virt: %p\n",vma->vm_flags,virt_to_phys(buffer),buffer);

    /* We don't care about forking processes (at least yet) */
    vma->vm_ops = &simple_remap_vm_ops;
    simple_vma_open(vma);
    return 0;
}

static int mmaptest_open(struct inode *inode, struct file *filp){
    unsigned int i=0;
    //buffer = ioremap_nocache(BUFFER_ADDRESS,maxframebuffer);
    buffer =  __get_free_pages(GFP_USER | GFP_DMA, get_order(maxframebuffer));
    printk("allocated: %ld @virt: %p @phys: %p virtToPhys(): %p\n",maxframebuffer,buffer,BUFFER_ADDRESS,virt_to_phys(buffer));
    for(i=0; i<maxframebuffer; i++)
        buffer[i] = (char)(maxframebuffer - i);
    return 0;
}

int mmaptest_release(struct inode *inode, struct file *filp){
    unsigned int i;
    for(i=0; i< 32; i++){
        printk(" %.02x ",buffer[i] );
    }
    printk("\n");
    free_pages(buffer,get_order(maxframebuffer));
    //iounmap(buffer);
    return 0;
}

static struct file_operations mmaptest_fops = {
    .owner   = THIS_MODULE,
    .open    = mmaptest_open,
    .release = mmaptest_release,
    .mmap  = mmaptest_mmap,
};

static int mmaptest_init_module(void) {
    int result;
    printk(KERN_INFO DEVNAME "module loading...\n");
    result = register_chrdev(major, DEVNAME, &mmaptest_fops);
    if ( result < 0 ) {
        printk(KERN_ERR DEVNAME ": unable to register character device\n");
        unregister_chrdev( major, DEVNAME );
        return -1;
    }
    if (!major) {
        major = result;
    }

    printk(KERN_INFO "Register initialized\n");
    printk(KERN_INFO DEVNAME " device: %d registered \n",major);
    return 0;
}

static void mmaptest_cleanup_module(void) {
    //printk(KERN_INFO DEVNAME " module unloaded\n");
    unregister_chrdev(major, DEVNAME );
}


module_init(mmaptest_init_module);
module_exit(mmaptest_cleanup_module);
