#ifndef LPCIE_H
#define LPCIE_H

#define LPCIE_DEVNAME_SIZE      32
#define LPCIE_SERIAL_SIZE       32




#define LPCIE_DMA_ALIGNMENT         FILE_LONG_ALIGNMENT


#define LPCIE_DMA_MAX_PAGE_SIZE     (8*1024*1024)
#define LPCIE_DMA_MAX_PAGES_CNT     252
#define LPCIE_MAX_PACKET_SIZE       32

#define LPCIE_MAX_TRANSF_CNT        4

#define LPCIE_MAX_PARTS_PER_TRANSF  (LPCIE_DMA_MAX_PAGES_CNT/LPCIE_MAX_TRANSF_CNT)

#define LPCIE_DMA_TRANSFER_LEN      (LPCIE_DMA_MAX_PAGES_CNT/LPCIE_MAX_TRANSF_CNT)*4096  //2*128*1024



#define L502_DEFAULT_ADC_PAGE_SIZE       (32*4096)
#define L502_DEFAULT_ADC_PAGES_CNT       32
#define L502_DEFAULT_ADC_PACKET_SIZE     (LPCIE_MAX_PACKET_SIZE)
#define L502_DEFAULT_ADC_IRQ_STEP        (8*4096)


#define L502_DEFAULT_DAC_PAGE_SIZE       (2*4096)
#define L502_DEFAULT_DAC_PAGES_CNT       16
#define L502_DEFAULT_DAC_PACKET_SIZE     (LPCIE_MAX_PACKET_SIZE)
#define L502_DEFAULT_DAC_IRQ_STEP        (4096)




#ifdef _WIN32
    typedef ULONG   uint32_t;
    typedef USHORT  uint16_t;
    typedef UCHAR   uint8_t;
    typedef LONG    int32_t;
    typedef SHORT   int16_t;
    typedef CHAR    int8_t;

    typedef LONGLONG    t_lpcie_bus_addr;
    typedef LONG        t_atomic;
    typedef WDFSPINLOCK t_spinlock;

    typedef  NTSTATUS t_status;
    #define IS_SUCCESS(status)  NT_SUCCESS(status)
#else
    #include <linux/types.h>
    #include <linux/cdev.h>
    #include <linux/list.h>
    #include <linux/dma-mapping.h>
    #include <linux/interrupt.h>
    #include <linux/mutex.h>
    #include <linux/fs.h>
    #include <linux/workqueue.h>

    //typedef __u32   uint32_t;
    //typedef __u16   uint16_t;
    //typedef __u8    uint8_t;
    //typedef __s32   int32_t;
    //typedef __s16   int16_t;
    //typedef __s8    int8_t;

    typedef dma_addr_t t_lpcie_bus_addr;
    typedef atomic_t t_atomic;
    typedef spinlock_t t_spinlock;

    typedef int t_status;



    #define IS_SUCCESS(status) !status

    #define STATUS_SUCCESS                          0
    #define STATUS_UNSUCCESSFUL                     -EFAULT
    #define STATUS_DEVICE_CONFIGURATION_ERROR       -EIO
    #define STATUS_INTERNAL_ERROR                   -EFAULT
    #define STATUS_INVALID_PARAMETER                -EFAULT
    #define STATUS_INVALID_DEVICE_STATE             -EIO
    #define STATUS_INSUFFICIENT_RESOURCES           -ENOMEM
#endif




/** состояния буфера DMA */
typedef enum {
    LPCIE_DMA_BUF_STATE_INVALID, /* буфер не выделен */
    LPCIE_DMA_BUF_STATE_ALLOCATED, /* буфер выделен, но передача не идет */
    LPCIE_DMA_BUF_STATE_SINGLESHOT, /* буфер выделен и запущен однократный сбор */
    LPCIE_DMA_BUF_STATE_RUN_STREAM, /* буфер выделен и используется потоковой ввод-вывод */
    LPCIE_DMA_BUF_STATE_RUN_CYCLE,  /* буфер выделен и используется для вывода циклических данных */
    LPCIE_DMA_BUF_STATE_LOAD_CYCLE /* буфер выделен и используется для загрузки циклических данных */
} t_lpcie_dma_buf_state;

/** событие для буфера DMA */
typedef enum {
    LPCIE_EVT_TYPE_NONE=0, /* не запланировано никакое событие */
    LPCIE_EVT_TYPE_NEXT_BUF=1, /* запланировано переключение буфера */
    LPCIE_EVT_TYPE_STOP=2 /* запланирован останов сбора */
} t_lpcie_evt_type;

#define LPCIE_DMA_IS_RUNNING(state) ((state==LPCIE_DMA_BUF_STATE_RUN_STREAM) \
                                   || (state==LPCIE_DMA_BUF_STATE_RUN_CYCLE) \
                                    || (state==LPCIE_DMA_BUF_STATE_SINGLESHOT))


#ifndef _WIN32
    typedef struct {
        struct page *page;
    } t_lpcie_dma_page;
#endif


/** стркутура, описывающая часть DMA-транзакции,
    непрерываную в физических адресах */
typedef struct lpcie_dma_part {
    uint32_t size; /**< размер (в словах) */
    t_lpcie_bus_addr bus_addr; /**< адрес на PCI-шине */
#ifndef _WIN32
    struct page* page; /**< описание страницы в памяти PC */
#endif
} t_lpcie_dma_part;

/** описание одного используемого буфера для DMA */
typedef struct dma_buf_param {
    size_t size; /**< полный размер буфера (в словах) */
    size_t pc_pos; /**< текущая позиция, откуда будут считываться
                        или записываться данные пользовательской программой*/
    size_t rdy_size; /**< сколько отсчетов доступно на чтение/запись пользовательской
                          программе */
    size_t transf_pos; /**< позиция, с которой будет запущена следующая транзакция DMA */
    size_t transf_progr_size; /**< на сколько отсчетов запущено в сумме DMA,
                                  но операция еще не заершилась */
#ifdef _WIN32
    uint32_t* buf;
    PMDL mdl;
#else
    t_lpcie_dma_page *pages; /**< массив страниц, из которых состоит буфер */
    uint32_t pages_cnt; /**< количестов страниц */
#endif
    t_lpcie_dma_buf_state state;
    uint32_t irq_step;
    uint32_t mul; /**< множитель для циклического сигнала */
    uint32_t sw_evt; /**< указывает по какому событию должно выполнится действие из evt_type */
    uint32_t evt_type; /**< указывает какое событие запланировано (если имеется) */
} t_lpcie_dma_buf_param;


/** структура, описывающая одну транзакцию DMA */
typedef struct dma_transf {
    uint32_t parts_cnt; /**< количество частей */
    t_lpcie_dma_part parts[LPCIE_DMA_MAX_PAGES_CNT]; /**< описание частей */
    uint32_t proc_size; /**< сколько обработано */
    uint32_t full_size; /**< полный размер */
#ifdef _WIN32
    WDFDMATRANSACTION transact; /**< объект транзакции WDF */
    int valid; /**< признак дейстительности поля transact */
#endif
    int run; /**< признак, что запущена передача данных по транзакции */
    t_lpcie_dma_buf_param* buf; /**< буфер к которому относится транзакция */
} t_dma_transf;


#ifdef _WIN32
typedef enum {
    LPCIE_DMA_DIR_IN = WdfDmaDirectionReadFromDevice,
    LPCIE_DMA_DIR_OUT = WdfDmaDirectionWriteToDevice
} t_lpcie_dma_direction;
#else
typedef enum {
    LPCIE_DMA_DIR_IN = DMA_FROM_DEVICE,
    LPCIE_DMA_DIR_OUT = DMA_TO_DEVICE
} t_lpcie_dma_direction;
#endif

typedef struct lpcie_dma_state {
    t_lpcie_dma_buf_param buf_param[2]; /** состояние буферов (для ЦАП может быть 2 для
                                      смены циклического сигнала) */
    t_lpcie_dma_buf_param* devbuf_params; /** состояние буфера, используемого устройством */
    t_lpcie_dma_buf_param* pcbuf_params; /** состояние буфера, используемого программой */
    t_lpcie_dma_buf_param* buf_to_free; /** буфер, который нужно освободить в фоновой задаче */

    t_dma_transf transf[LPCIE_MAX_TRANSF_CNT]; /** состояние текущей запущенной передачи по DMA.
                                используются две транзакции (пока одна перезапускается,
                                другая выполняется) */

    uint32_t start_transf_id; /** номер последней запущенной транзакции */
    uint32_t init_transf_id; /** номер последней инициализированной транзации */
    uint32_t done_transf_id; /** номер транзакции, которая будет завершена следующей */

    uint32_t done_page; /** номер странцы в устройстве, с которой начинается
                            транзация, которая будет завершена следующей */
    uint32_t start_page; /** номер страницы в устройстве, которая будет
                             использована для следующей транзакции*/


    uint32_t ch; /** номер канала DMA */
    t_lpcie_dma_direction dir; /** направление передачи по каналу */

    t_spinlock param_lock; /** spinlock для изменения параметров состояния буфера и передачи */
    t_spinlock state_lock;
    int req_valid; /** признак действительности запроса */
#ifdef _WIN32
    WDFREQUEST req; /** запрос от пользовательской программы */
    WDFWORKITEM work;
    WDFWAITLOCK work_lock;
    t_atomic work_enqeued;
#else
    wait_queue_head_t queue; /** очередь, на которой User ждет данные */
    struct semaphore sem; /** семафор, для доступа к данным со стороны User-Space */
    //spinlock_t lock;

#endif
} t_lpcie_dma_state;


typedef struct dma_cap {
    uint16_t max_packet_size;
    uint16_t max_pages_cnt;
    uint32_t max_page_size;
    uint32_t ch_cnt;
} t_dma_cap;

typedef enum {
    LPCIE_DEV_FLAGS_BF  = 0x01,
    LPCIE_DEV_FLAGS_DAC = 0x02,
    LPCIE_DEV_FLAGS_GAL = 0x04
} lpcie_dev_flags;



/** информация о выделенной области памяти PCI (из BAR) */
typedef struct bar_info {
#ifdef _WIN32
    uint32_t* regs;
    size_t len;
#else
    unsigned long start; /** начальный физический адрес */
    unsigned long len;  /** размер области (в байтах) */
    void* __iomem buf; /** виртуальный адрес, используемый ядром для доступа */
#endif
} t_bar_info;

typedef struct _LPCIE_DEV_EXTENSION {
    uint16_t vid; /** Vendor ID устройства */
    uint16_t pid; /** Product ID устройства */
    lpcie_dev_flags devflags; /** флаги присутствующих опций */
    char name[LPCIE_DEVNAME_SIZE]; /** название устройства */
    char sn[LPCIE_SERIAL_SIZE]; /** серийный номер */


    t_lpcie_dma_state dma[2];


    t_dma_cap dma_cap;
    uint32_t irq_state;

    size_t in_transf_len;
    size_t out_transf_len;

#ifdef _WIN32
    t_atomic is_opened; /** признак, открыто ли устройство (для запрета двойного открытия) */
    WDFDEVICE device; /** WDF объект устройства */
    WDFQUEUE ctlQueue; /** Последовательная очередь для управляющих запросов */
    WDFQUEUE rwQueue; /** Параллельная очередь для запросов ввода-вывода к файлам
                          устройства. Запросы для устройства направляются в
                          readDevQueue/writeDevQueue, чтобы их сериализовать.
                          Запросы к информационным файлам выполняются параллельно */
    WDFQUEUE readDevQueue; /** последовательная очередь для запросов чтения из устройства */
    WDFQUEUE writeDevQueue; /** последовательная очередь для запросов записи в устройство */
    WDFDMAENABLER dmaEnabler[LPCIE_MAX_TRANSF_CNT];
    WDFINTERRUPT interrupt; /** Объект прерывания */
#else
    char devname[10];
    t_atomic is_rdy; /** признак, открыто ли устройство */
    dev_t devnum; /** номер устройства (из двух частей - номер драйвера и номер экземпляра) */
    struct pci_dev* pci_dev; /** Указатель на структуру ядра linux с информаций
                                 о pci-устройстве */
    struct device* devclass; /** объект устройства, созданный для класса lpcie */
    struct cdev cdev; /** объект символьного устройства */
    uint8_t use_msi; /**< Признак, что используются msi-прерывания (чтобы не забыть освободить потом) */
    uint8_t use_irq; /**< Признак, что прерывание было успешно выделено устройству */
    struct list_head list; /**< указатель на следующий элемент списка устройств */
    int in_list; /** признак, что данное устройство добавлено в общий список устройств и поле list действительно */

    t_spinlock irq_lock;
    //struct tasklet_struct work;
    struct workqueue_struct *workqueue;
    struct work_struct work;

#endif

    t_bar_info fpga_regs;
} t_lpcie_dev_context;

#ifdef _WIN32
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(t_lpcie_dev_context, LpcieGetDeviceContext);
#endif





t_status lpcie_fpga_reg_read(t_lpcie_dev_context* devExt, uint32_t addr,
                              uint32_t* val);
t_status lpcie_fpga_reg_write(t_lpcie_dev_context* devExt, uint32_t addr,
                              uint32_t val);

t_status lpcie_read_descr(t_lpcie_dev_context* dev);


typedef struct {
    size_t req_size;
    size_t proc_size;

#ifdef _WIN32
    t_lpcie_dma_state* state;
    WDFMEMORY buf;
    //uint32_t* addr;
    t_lpcie_dev_context *dev;
#else
    char __user *addr;
#endif
} t_lpcie_req_context;
#ifdef _WIN32
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(t_lpcie_req_context, LpcieGetReqContext);
#endif


#ifdef _WIN32
typedef enum {
    LPCIE_FILETYPE_DEVICE       = 0,
    LPCIE_FILETYPE_NAME         = 1,
    LPCIE_FILETYPE_SN           = 2,
    LPCIE_FILETYPE_OPENED       = 3,
    LPCIE_FILETYPE_BF_PRESENT   = 4,
    LPCIE_FILEYTPE_DAC_PRESENT  = 5,
    LPCIE_FILETYPE_GAL_PRESENT  = 6
} t_lpcie_file_type;

typedef NTSTATUS (*t_lpcie_file_read)(t_lpcie_dev_context* devExt, PVOID buf,
                                      size_t len, ULONG* rd_done);

typedef struct {
    PCWSTR name;
    t_lpcie_file_type ftype;
    t_lpcie_file_read read;
} t_lpcie_file_descr;

typedef struct _LPCIE_FILE_CONTEXT {
   t_lpcie_file_descr descr;
   t_lpcie_dev_context *device;
} LPCIE_FILE_CONTEXT, *PLPCIE_FILE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(LPCIE_FILE_CONTEXT, LpcieGetFileContext);


typedef struct {
    t_lpcie_dev_context* dev;
    t_lpcie_dma_state* state;
    uint32_t irq_state;
} t_lpcie_workitem_context;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(t_lpcie_workitem_context, LpcieGetWorkItemContext);

#endif

#endif // LPCIE_H
