#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/version.h>

#include "lpcie_trace.h"
#include "lpcie_drv.h"
#include "lpcie_streams.h"
#include "lpcie_ioctls.h"
#include "lpcie_version.h"


#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11))
/** старый вариант ioctl в ядре до 2.6.11 - использующий BKL */
static int lpcie_ioctl(struct inode *inode, struct file *pfile,
                        unsigned int cmd, unsigned long arg) {
    return lpcie_ioctl_unlock(pfile, cmd, arg);
}
#endif

/** обработка управляющих запросов */
long lpcie_ioctl_unlock(struct file *pfile, unsigned int cmd, unsigned long arg) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,19,0)
        t_lpcie_dev_context* dev = container_of(pfile->f_dentry->d_inode->i_cdev,
                                            t_lpcie_dev_context, cdev);
#else
        t_lpcie_dev_context* dev = container_of(pfile->f_path.dentry->d_inode->i_cdev,
                                            t_lpcie_dev_context, cdev); 
#endif
    long err = 0;


    switch (cmd) {
        case LPCIE_IOCTL_GET_DRV_VERSION: {
                uint32_t ver = (LPCIE_DRIVER_VER_MAJOR << 24) |
                               (LPCIE_DRIVER_VER_MINOR << 16) |
                               (LPCIE_DRIVER_VER_REV   << 8) |
                               LPCIE_DRIVER_VER_BUILD;
                err = copy_to_user((uint32_t*)arg, &ver, sizeof(ver));
                if (err) {
                    PERROR("copy_to_user error!");
                }
            }
            break;
        case LPCIE_IOCTL_MEMFPGA_RD: {
                t_lpcie_mem_rw rw;
                if (!arg || copy_from_user(&rw, (t_lpcie_mem_rw*)arg, sizeof(rw.addr))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_fpga_reg_read(dev, rw.addr, &rw.val);
                    if (!err) {
                        err = copy_to_user((t_lpcie_mem_rw*)arg, &rw, sizeof(rw));
                    }
                }
            }
            break;
        case LPCIE_IOCTL_MEMFPGA_WR: {
                t_lpcie_mem_rw rw;
                if (!arg || copy_from_user(&rw, (t_lpcie_mem_rw*)arg, sizeof(rw))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_fpga_reg_write(dev, rw.addr, rw.val);
                }
            }
            break;
        case LPCIE_IOCTL_RELOAD_DEVINFO:
            err = lpcie_read_descr(dev);
            break;
        case LPCIE_IOCTL_STREAM_SET_PARAMS: {
                t_lpcie_stream_ch_params par;
                if (!arg || copy_from_user(&par, (t_lpcie_mem_rw*)arg, sizeof(par))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_streams_set_params(dev, &par, sizeof(par));
                }
            }
            break;
        case LPCIE_IOCTL_STREAM_GET_PARAMS: {
                t_lpcie_stream_ch_params par;
                if (!arg || copy_from_user(&par, (t_lpcie_stream_ch_params*)arg, sizeof(par.ch))) {
                    err = -EFAULT;
                } else {
                    size_t size = sizeof(par);
                    err = lpcie_streams_get_params(dev, par.ch, &par, &size);
                    if (!err) {
                        err = copy_to_user((t_lpcie_stream_ch_params*)arg, &par, sizeof(par));
                    }
                }
            }
            break;
        case LPCIE_IOCTL_STREAM_GET_RDY_SIZE: {
                t_lpcie_get_rdy_par par;
                if (!arg || copy_from_user(&par, (t_lpcie_stream_ch_params*)arg, sizeof(par.ch))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_stream_get_rdy_size(dev, par.ch, &par.rdy_size);
                    if (!err) {
                        err = copy_to_user((t_lpcie_stream_ch_params*)arg, &par, sizeof(par));
                    }
                }
            }
            break;
        case LPCIE_IOCTL_STREAM_START: {
                uint32_t ch;
                if (!arg || copy_from_user(&ch, (uint32_t*)arg, sizeof(ch))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_stream_start(dev, ch, 0);
                }
            }
            break;
        case LPCIE_IOCTL_STREAM_START_SINGLE: {
                uint32_t ch;
                if (!arg || copy_from_user(&ch, (uint32_t*)arg, sizeof(ch))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_stream_start(dev, ch, 1);
                }
            }
            break;
        case LPCIE_IOCTL_STREAM_STOP: {
                uint32_t ch;
                if (!arg || copy_from_user(&ch, (uint32_t*)arg, sizeof(ch))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_stream_stop(dev, ch);
                }
            }
            break;
        case LPCIE_IOCTL_STREAM_FREE: {
                uint32_t ch;
                if (!arg || copy_from_user(&ch, (uint32_t*)arg, sizeof(ch))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_stream_free(dev, ch);
                }
            }
            break;
        case LPCIE_IOCTL_CYCLE_LOAD: {
                t_lpcie_cycle_set_par par;
                if (!arg || copy_from_user(&par, (uint32_t*)arg, sizeof(par))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_stream_cycle_load(dev, &par);
                }
            }
            break;
        case LPCIE_IOCTL_CYCLE_SWITCH: {
                t_lpcie_cycle_evt_par par;
                if (!arg || copy_from_user(&par, (uint32_t*)arg, sizeof(par))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_stream_cycle_switch(dev, &par);
                }
            }
            break;
        case LPCIE_IOCTL_CYCLE_STOP: {
                t_lpcie_cycle_evt_par par;
                if (!arg || copy_from_user(&par, (uint32_t*)arg, sizeof(par))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_stream_cycle_stop(dev, &par);
                }
            }
            break;
        case LPCIE_IOCTL_CYCLE_CHECK_SETUP: {
                t_lpcie_cycle_check_setup_par par;
                if (!arg || copy_from_user(&par, (uint32_t*)arg, sizeof(par))) {
                    err = -EFAULT;
                } else {
                    err = lpcie_stream_cycle_setup_done(dev, par.ch, &par.done);
                    if (!err) {
                        err = copy_to_user((t_lpcie_cycle_check_setup_par*)arg, &par, sizeof(par));
                    }
                }
            }
            break;
        default:
            PERROR("unsupported ioctl= 0x%x!", cmd);
            err = -ENOTTY;
            break;
    }
    return err;
}


