#ifndef LPCIE_FILES_C
#define LPCIE_FILES_C


#include <linux/module.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/poll.h>


#include "lpcie_trace.h"
#include "lpcie_drv.h"
#include "lpcie_streams.h"
#include "l502_fpga_regs.h"

/* объявление аттрибутов устройства, доступных как файлы в /sys/class/lpcie/lpcieN/
    и функций для чтения содержимого этих файлов */
static ssize_t f_lpcie_show_name(struct device *class_dev, struct device_attribute *attr, char *buf);
static ssize_t f_lpcie_show_serial(struct device *class_dev, struct device_attribute *attr, char *buf);
static ssize_t f_lpcie_show_is_opened(struct device *class_dev, struct device_attribute *attr, char *buf);
static ssize_t f_lpcie_show_bf_present(struct device *class_dev, struct device_attribute *attr, char *buf);
static ssize_t f_lpcie_show_dac_present(struct device *class_dev, struct device_attribute *attr, char *buf);
static ssize_t f_lpcie_show_gal_present(struct device *class_dev, struct device_attribute *attr, char *buf);


static DEVICE_ATTR(name, S_IRUGO, f_lpcie_show_name, NULL);
static DEVICE_ATTR(sn, S_IRUGO, f_lpcie_show_serial, NULL);
static DEVICE_ATTR(opened, S_IRUGO, f_lpcie_show_is_opened, NULL);
static DEVICE_ATTR(bf, S_IRUGO, f_lpcie_show_bf_present, NULL);
static DEVICE_ATTR(dac, S_IRUGO, f_lpcie_show_dac_present, NULL);
static DEVICE_ATTR(gal, S_IRUGO, f_lpcie_show_gal_present, NULL);


/** Функция открытия устройства */
int lpcie_open(struct inode *inode, struct file *pfile) {
    int err = 0;
    /* послучаем указатель на открываемое устройство */
    t_lpcie_dev_context* dev = container_of(inode->i_cdev, t_lpcie_dev_context, cdev);

    PDEBUG("open device %s (%s)\n", dev->name, dev->sn);

    /* при попытке открыть на чтение, проверяем, что на чтение
        устройство уже не было открыто */
    if (!atomic_dec_and_test(&dev->is_rdy)) {
        atomic_inc(&dev->is_rdy);
        err = -EBUSY;
        PERROR("read access deny\n");
    } else {
        /* указываем, что не поддерживаем lseek */
        nonseekable_open(inode, pfile);
        pfile->private_data = dev;
    }

    return err;
}

/** Закрытие устройства */
int lpcie_release(struct inode *inode, struct file *pfile) {
    /* послучаем указатель на открываемое устройство */
    t_lpcie_dev_context* dev = container_of(inode->i_cdev, t_lpcie_dev_context, cdev);

    PDEBUG("lpcie device release\n");

    cancel_work_sync(&dev->work);
    lpcie_streams_dev_release(dev);

    /* освобождаем доступ на чтение/запись устройства */
    atomic_inc(&dev->is_rdy);

    return 0;
}



static ssize_t f_proc_req(struct file *pfile, uint32_t ch, char __user *buff, size_t count) {
    t_lpcie_dev_context* dev = pfile->private_data;
    int err = 0;
    int ret0 = 0;
    t_lpcie_req_context req;
    t_lpcie_dma_state *state = &dev->dma[ch];

    if (down_interruptible(&state->sem))
        return -ERESTARTSYS;

    req.req_size = count/4;
    req.addr = buff;
    req.proc_size = 0;

    while (!err && !ret0 && !req.proc_size) {
        /* пробуем прочитать данные из буфера DMA*/
        err = lpcie_proc_req(dev, state, &req);
        /* если ничего не прочитали... */
        if (req.proc_size==0) {

            if (!LPCIE_DMA_IS_RUNNING(state->pcbuf_params->state)) {
                /* если сбор останолен, то нужно вернуть нулевое значение,
                    как признак конца файла */
                ret0 = 1;
            } else if (pfile->f_flags & O_NONBLOCK) {
                /* при неблокируемом чтении возвращаемся сразу */
                err = -EAGAIN;
            } else {
                /* при блокируемом чтении - ждем, пока будут данные */
                if (wait_event_interruptible(state->queue, LPCIE_STREAM_PC_RDY(state))) {
                    err = -ERESTARTSYS;
                }
            }
        }
    }

    up(&state->sem);
    return err ? err : req.proc_size*4;
}


/* чтение данных из устройста */
ssize_t lpcie_read(struct file *pfile, char __user *buff, size_t count, loff_t *poffs) {
    return f_proc_req(pfile, L502_DMA_CHNUM_IN, buff, count);
}


ssize_t lpcie_write(struct file *pfile, const char __user *buff, size_t count, loff_t *poffs) {
    return f_proc_req(pfile, L502_DMA_CHNUM_OUT, (char __user*)buff, count);
}


unsigned int lpcie_poll (struct file *pfile, struct poll_table_struct *wait) {
    u32 rdy;
    unsigned int ret = 0;
    t_lpcie_dev_context* dev = pfile->private_data;

    lpcie_stream_get_rdy_size(dev, L502_DMA_CHNUM_IN, &rdy);

    if (rdy > 0) {
        ret |= POLLIN | POLLRDNORM;
    } else if (!LPCIE_DMA_IS_RUNNING(dev->dma[L502_DMA_CHNUM_IN].pcbuf_params->state)) {
        ret |= POLLHUP;
    }

    lpcie_stream_get_rdy_size(dev, L502_DMA_CHNUM_OUT, &rdy);

    if (rdy > 0) {
        ret |= POLLOUT | POLLWRNORM;
    }

    poll_wait(pfile, &dev->dma[L502_DMA_CHNUM_IN].queue, wait);
    poll_wait(pfile, &dev->dma[L502_DMA_CHNUM_OUT].queue, wait);

    return ret;
}



/** Создание дополнительных файлов-аттрибутов устройства, из которых
    можно прочитать информацию о устройстве */
int lpcie_create_spec_files(t_lpcie_dev_context* dev) {
    int err = 0;

    if (!err)
        err = device_create_file(dev->devclass, &dev_attr_name);
    if (!err)
        err = device_create_file(dev->devclass, &dev_attr_sn);
    if (!err)
        err = device_create_file(dev->devclass, &dev_attr_opened);
    if (!err)
        err = device_create_file(dev->devclass, &dev_attr_bf);
    if (!err)
        err = device_create_file(dev->devclass, &dev_attr_dac);
    if (!err)
        err = device_create_file(dev->devclass, &dev_attr_gal);

    return err;
}


static ssize_t f_lpcie_show_name(struct device *class_dev, struct device_attribute *attr, char *buf) {
    struct pci_dev* pci_dev = container_of(class_dev, struct pci_dev, dev);
    t_lpcie_dev_context* dev = pci_get_drvdata(pci_dev);
    return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name);
}

static ssize_t f_lpcie_show_serial(struct device *class_dev, struct device_attribute *attr, char *buf) {
    struct pci_dev* pci_dev = container_of(class_dev, struct pci_dev, dev);
    t_lpcie_dev_context* dev = pci_get_drvdata(pci_dev);
    return scnprintf(buf, PAGE_SIZE, "%s\n", dev->sn);
}

static ssize_t f_lpcie_show_is_opened(struct device *class_dev, struct device_attribute *attr, char *buf) {
    struct pci_dev* pci_dev = container_of(class_dev, struct pci_dev, dev);
    t_lpcie_dev_context* dev = pci_get_drvdata(pci_dev);
    char val = atomic_read(&dev->is_rdy) ? '0' : '1';
    return scnprintf(buf, PAGE_SIZE, "%c\n", val);
}

static ssize_t f_lpcie_show_bf_present(struct device *class_dev, struct device_attribute *attr, char *buf) {
    struct pci_dev* pci_dev = container_of(class_dev, struct pci_dev, dev);
    t_lpcie_dev_context* dev = pci_get_drvdata(pci_dev);
    return scnprintf(buf, PAGE_SIZE, "%c\n", dev->devflags & LPCIE_DEV_FLAGS_BF ? '1' : '0');
}

static ssize_t f_lpcie_show_dac_present(struct device *class_dev, struct device_attribute *attr, char *buf) {
    struct pci_dev* pci_dev = container_of(class_dev, struct pci_dev, dev);
    t_lpcie_dev_context* dev = pci_get_drvdata(pci_dev);
    return scnprintf(buf, PAGE_SIZE, "%c\n", dev->devflags & LPCIE_DEV_FLAGS_DAC ? '1' : '0');
}

static ssize_t f_lpcie_show_gal_present(struct device *class_dev, struct device_attribute *attr, char *buf) {
    struct pci_dev* pci_dev = container_of(class_dev, struct pci_dev, dev);
    t_lpcie_dev_context* dev = pci_get_drvdata(pci_dev);
    return scnprintf(buf, PAGE_SIZE, "%c\n", dev->devflags & LPCIE_DEV_FLAGS_GAL ? '1' : '0');
}




#endif // LPCIE_FILES_C
