#ifdef _WIN32
    #include <ntddk.h>
    #include <wdf.h>
#else
    #include <linux/module.h>
    #include <linux/slab.h>
    #include <linux/pci.h>
    #include <linux/wait.h>
    #include <linux/sched.h>
    #include <linux/highmem.h>
#endif

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

#ifdef _WIN32
    #include "lpcie_streams.tmh"
#endif


#define LPCIE_CYCLE_BUF_SIZE_MIN   (32*PAGE_SIZE)

#define LPCIE_TRANS_IND(id) (id%LPCIE_MAX_TRANSF_CNT)



static int f_program_transf(t_lpcie_dev_context* dev,  t_lpcie_dma_state *state, t_dma_transf *transf);
static t_status f_start_transact(t_lpcie_dev_context* dev, t_lpcie_dma_state* state);
static t_status f_dma_rst(t_lpcie_dev_context* dev, int ch);

//#define TraceEvents(lvl, val, ...)

#ifdef _WIN32
    #define spinlock_acquire(lock) WdfSpinLockAcquire(lock)
    #define spinlock_release(lock) WdfSpinLockRelease(lock)

    static t_status spinlock_create(t_lpcie_dev_context* dev, t_spinlock* lock) {
        t_status status;
        WDF_OBJECT_ATTRIBUTES attributes;
        WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
        attributes.ParentObject = dev->device;

        status = WdfSpinLockCreate(&attributes, lock);
        if(!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP,
                        "WdfSpinLockCreate failed: %!STATUS!", status);
        }
        return status;
    }

    #define  spinlock_delete(lock)

#else
    #define spinlock_acquire(lock) spin_lock(&lock)
    #define spinlock_release(lock) spin_unlock(&lock)
    #define  spinlock_delete(lock)


    static t_status spinlock_create(t_lpcie_dev_context* dev, t_spinlock* lock) {
        spin_lock_init(lock);
        return 0;
    }
#endif





#ifdef _WIN32
    #define LPCIE_PAGE_WRDS_CNT   1024U

    EVT_WDF_WORKITEM                    LpcieEvtWorkItem;
    EVT_WDF_PROGRAM_DMA                 EvtLpcieProgramDma;

    static t_status f_dma_free(t_lpcie_dev_context* dev, t_lpcie_dma_buf_param* param);


    VOID LpcieEvtWorkItem (IN WDFWORKITEM  WorkItem) {
        t_lpcie_workitem_context* context = LpcieGetWorkItemContext(WorkItem);
        //PAGED_CODE();
        context->state->work_enqeued = 0;
        lpcie_streams_proc_transf(context->dev, context->state->ch);

        /** @todo Доделать синхронизацию, чтобы не могло возникнуть гонок,
         *  когда мы очищаем в фоне буфер и устанавливаем его по запросу */
        if (context->state->buf_to_free) {
            t_lpcie_dma_buf_param* par = context->state->buf_to_free;
            context->state->buf_to_free = NULL;
            f_dma_free(context->dev, par);
        }

    }

    static t_status dma_state_init(t_lpcie_dev_context *dev, t_lpcie_dma_state* state) {
        WDF_WORKITEM_CONFIG   wi_cfg;
        WDF_OBJECT_ATTRIBUTES wi_attr;
        t_status              status;


        WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wi_attr, t_lpcie_workitem_context);
        wi_attr.ParentObject = dev->device;
        WDF_WORKITEM_CONFIG_INIT(&wi_cfg, LpcieEvtWorkItem);
        status = WdfWorkItemCreate( &wi_cfg, &wi_attr, &state->work);
        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT,
                        "WorkItem create error: %!STATUS!", status);
        } else {
            t_lpcie_workitem_context* context = LpcieGetWorkItemContext(state->work);
            context->state = state;
            context->irq_state = 0;
            context->dev = dev;
        }


        if (IS_SUCCESS(status)) {
            WDF_OBJECT_ATTRIBUTES attr;
            WDF_OBJECT_ATTRIBUTES_INIT(&attr);
            attr.ParentObject = dev->device;

            status = WdfWaitLockCreate(&attr, &state->work_lock);
            if (!IS_SUCCESS(status)) {
                TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT,
                            "Wait lock create error: %!STATUS!", status);
            } else {
                state->work_enqeued = 0;
            }
        }
        state->buf_to_free = NULL;
        return status;
    }

    static t_status f_release_transf(t_lpcie_dev_context *dev, t_lpcie_dma_state* state, t_dma_transf* transf) {
        t_status trans_status;
        WdfDmaTransactionDmaCompletedFinal(transf->transact, 0,
                                            &trans_status);
        transf->run=0;
        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA,
                "Terminate transaction with status : %!STATUS!",  trans_status);

        if (IS_SUCCESS(trans_status)) {
            trans_status = WdfDmaTransactionRelease(transf->transact);
        }
        return trans_status;
    }

#else

    #define LPCIE_PAGE_WRDS_CNT   (PAGE_SIZE/4)
    #define LPCIE_PAGE_WRDS_SHIFT  (PAGE_SHIFT-2)



    static t_status dma_state_init(t_lpcie_dev_context *dev, t_lpcie_dma_state* state) {
        t_status status = STATUS_SUCCESS;

        init_waitqueue_head(&state->queue);
        sema_init(&state->sem,1);

        return status;
    }

    static t_status f_release_transf(t_lpcie_dev_context *dev, t_lpcie_dma_state *state,
                                     t_dma_transf *transf) {
        uint16_t p;
        for (p=0; p < transf->parts_cnt; p++) {
            dma_unmap_page(&dev->pci_dev->dev,
                           transf->parts[p].bus_addr,
                           transf->parts[p].size*4,
                           state->dir);
        }
        transf->run = 0;
        return STATUS_SUCCESS;
    }
#endif




/** структура, описывающая возможности DMA-блока для каждого устройства */
typedef struct {
    uint16_t vid;
    uint16_t pid;
    const char *devname;
    t_dma_cap dma_cap;
} t_lpcie_dev_cap;

static t_lpcie_dev_cap f_devcap_table[] = {
    {0x1172, 0x0502, "L502",
        {LPCIE_MAX_PACKET_SIZE, L502_MAX_PAGES_CNT, LPCIE_DMA_MAX_PAGE_SIZE, 2}}
};




/* останов потока данных по каналу DMA */
static t_status f_dma_stop(t_lpcie_dev_context* dev, int ch) {
    t_status status = STATUS_SUCCESS;
    uint32_t reg = (1<<ch);
    int i;

    /* запрещаем канал */
    lpcie_fpga_reg_write(dev, L502_REGS_DMA_DIS, 1<<ch);
    /* проверяем, что канал остановился */
    for (i=0; (i < 10) && (reg & (1<<ch)); i++) {
        lpcie_fpga_reg_read(dev, L502_REGS_DMA_EN, &reg);
    }

    if (reg & (1<<ch)) {
        /* если так и не остановился => ошибка */
        TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA, "Cannot stop dma channel %d!!", ch);
        status = STATUS_INTERNAL_ERROR;
    } else {
        uint32_t t;

        lpcie_fpga_reg_write(dev, L502_REGS_DMA_IRQ_DIS, L502_REGBIT_DMA_IRQ_PAGE_Msk(ch) | L502_REGBIT_DMA_IRQ_FLUSH_Msk(ch));

        spinlock_acquire(dev->dma[ch].state_lock);
        /* помечаем буферы как выделенные, но не используемые для передачи */
        if (LPCIE_DMA_IS_RUNNING(dev->dma[ch].devbuf_params->state))
            dev->dma[ch].devbuf_params->state = LPCIE_DMA_BUF_STATE_ALLOCATED;
        if (LPCIE_DMA_IS_RUNNING(dev->dma[ch].pcbuf_params->state))
            dev->dma[ch].pcbuf_params->state = LPCIE_DMA_BUF_STATE_ALLOCATED;


        /* останавливаем и удаляем все транзакции, которые были запущены */
        for (t=0; t < LPCIE_MAX_TRANSF_CNT; t++) {
#ifdef _WIN32
            if (dev->dma[ch].transf[t].valid) {
#endif
                if (dev->dma[ch].transf[t].run) {
                    f_release_transf(dev, &dev->dma[ch], &dev->dma[ch].transf[t]);
                }
#ifdef _WIN32
                WdfObjectDelete(dev->dma[ch].transf[t].transact);
                dev->dma[ch].transf[t].valid = 0;
            }
#endif
        }

        spinlock_release(dev->dma[ch].state_lock);

        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA,
                    "Stop DMA channel %d successfully", ch);

        /* для канала на вывод очищаем буфер, чтобы он случайно не использовался
         * в дальнейшем */
        if (dev->dma[ch].dir==LPCIE_DMA_DIR_OUT)
            f_dma_rst(dev, ch);

        /* на случай, если ждем данные - пробуждаем поток, указывая что не дождемся уже */
#ifndef _WIN32
        wake_up_interruptible(&dev->dma[ch].queue);
#endif
    }
    return status;
}


/* освобождение буферов DMA для заданного канала*/
static t_status f_dma_free(t_lpcie_dev_context *dev, t_lpcie_dma_buf_param *param) {
    t_status status = STATUS_SUCCESS;
    if (param->state != LPCIE_DMA_BUF_STATE_INVALID) {
#ifdef _WIN32
        IoFreeMdl(param->mdl);
        MmFreeNonCachedMemory(param->buf, param->size*4);
        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA,
                    "Free dma buffer. size = %d, va = %p", param->size, param->buf);
#else
        if (param->pages) {
            u32 p;
            for (p=0; p < param->pages_cnt; p++) {
                __free_page(param->pages[p].page);
            }

            kfree(param->pages);
            param->pages = 0;
        }

        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA,
                    "Free dma buffer. size = %d, pages = %d", (uint32_t)param->size, param->pages_cnt);
#endif
        param->state = LPCIE_DMA_BUF_STATE_INVALID;
        param->sw_evt = 0;
        param->evt_type = LPCIE_EVT_TYPE_NONE;
    }
    return status;
}

static void f_dma_buf_param_init(t_lpcie_dev_context *dev, int ch, t_lpcie_dma_buf_param *par) {
    par->pc_pos = 0;
    par->transf_pos = 0;
    par->transf_progr_size = 0;
    par->rdy_size = dev->dma[ch].dir == LPCIE_DMA_DIR_IN ? 0 : par->size;
    par->sw_evt = 0;
    par->evt_type = LPCIE_EVT_TYPE_NONE;
}

/* выделение буфера DMA для канала */
static t_status f_dma_setup(t_lpcie_dev_context *dev, int ch, size_t size,
                            uint32_t irq_step,int flags) {
    t_status status = STATUS_SUCCESS;
    t_lpcie_dma_buf_param *param = dev->dma[ch].pcbuf_params;

    /* если размер не соответствует ранее установленному, то очищаем
     * старый буфер, чтобы выделить новый */
    if (param->size!=size) {
        f_dma_free(dev, param);
    }

    /* если буфер не выделен - выделяем новых буфер */
    if (param->state == LPCIE_DMA_BUF_STATE_INVALID) {
#ifdef _WIN32
        param->buf = MmAllocateNonCachedMemory(size*4);
        if (param->buf == NULL) {
            TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA,
                        "Dma buffer create error");
            status = STATUS_INSUFFICIENT_RESOURCES;
        } else {
            TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA,
                        "Allocate dma buffer. size = %d, va = %p", size, param->buf);
            param->mdl = IoAllocateMdl(param->buf, size*4, FALSE, FALSE, 0);
            if (param->mdl == NULL) {
                TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA,
                            "Mdl allocate error");
                MmFreeNonCachedMemory(param->buf, size*4);
                status = STATUS_INSUFFICIENT_RESOURCES;
            } else {
                MmBuildMdlForNonPagedPool(param->mdl);
            }
        }
#else
        /* В Linux буфер выделяем по страницам, чтобы он мог быть сильно
         * фрагментирован. Для хранения выделенных страниц, выделяем
         * отдельную память */
        param->pages_cnt = (size + LPCIE_PAGE_WRDS_CNT-1)/LPCIE_PAGE_WRDS_CNT;
        param->pages = kmalloc(sizeof(param->pages[0])*param->pages_cnt, GFP_KERNEL);
        if (param->pages) {
            uint32_t page;
            TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA,
                        "Allocate dma buffer! size = %d, pages = %d", (uint32_t)size, param->pages_cnt);

            for (page=0; (page < param->pages_cnt) && IS_SUCCESS(status); page++) {
                param->pages[page].page = alloc_page(GFP_KERNEL);
                if (!param->pages[page].page) {
                    int p;
                    TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA,
                                "Memory page allocation error!");
                    status = STATUS_INSUFFICIENT_RESOURCES;

                    /* очищаем выделенные до этого страницы... */
                    for (p=0; p < page; p++) {
                        __free_page(param->pages[p].page);
                        param->pages[p].page = 0;
                    }
                    kfree(param->pages);
                    param->pages = 0;
                    param->pages_cnt = 0;
                    param->size = 0;
                }
            }
        } else {
            TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA,
                        "Memory allocation for dma pages error!");
            status = STATUS_INSUFFICIENT_RESOURCES;
        }
#endif
    }

    if (IS_SUCCESS(status)) {
        param->size = size;
        param->state = LPCIE_DMA_BUF_STATE_ALLOCATED;

        f_dma_buf_param_init(dev, ch, param);

        if (irq_step > dev->dma[ch].pcbuf_params->size/LPCIE_MAX_TRANSF_CNT)
            irq_step = dev->dma[ch].pcbuf_params->size/LPCIE_MAX_TRANSF_CNT;
        if (irq_step > (dev->dma_cap.max_pages_cnt*LPCIE_PAGE_WRDS_CNT/LPCIE_MAX_TRANSF_CNT))
            irq_step = dev->dma_cap.max_pages_cnt*LPCIE_PAGE_WRDS_CNT/LPCIE_MAX_TRANSF_CNT;
#ifdef _WIN32
        if (dev->dma[ch].dir == LPCIE_DMA_DIR_IN) {
            if (irq_step > dev->in_transf_len/4)
                irq_step = dev->in_transf_len/4;
        } else {
            if (irq_step > dev->out_transf_len/4)
                irq_step = dev->out_transf_len/4;
        }
#endif
        param->irq_step = irq_step;

        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "irq step = %d", irq_step);

        lpcie_fpga_reg_write(dev, L502_REGS_DMA_CH_CMP_CNTR(ch), 0);

        lpcie_fpga_reg_write(dev, L502_REGS_DMA_CH_CTL(ch),
            L502_REGBIT_DMA_CTL_PC_WAIT_Msk |
            ((dev->dma_cap.max_packet_size <<L502_REGBIT_DMA_CTL_PACK_SIZE_Pos) & L502_REGBIT_DMA_CTL_PACK_SIZE_Msk) |
            ((dev->dma_cap.max_pages_cnt <<L502_REGBIT_DMA_CTL_PAGE_CNT_Pos) & L502_REGBIT_DMA_CTL_PAGE_CNT_Msk)
            );
    }

    return status;
}

/* сброс параметров DMA для запуска с нуля */
static t_status f_dma_rst(t_lpcie_dev_context* dev, int ch) {
    /* сбрасываем текущие счетчики в DMA-блококе и сбрасываем временный буфер */
    lpcie_fpga_reg_write(dev, L502_REGS_DMA_CH_CUR_POS(ch), 0);
    lpcie_fpga_reg_write(dev, L502_REGS_DMA_CH_CUR_CNTR(ch), 0);
    lpcie_fpga_reg_write(dev, L502_REGS_DMA_CH_PC_POS(ch), 0);
    lpcie_fpga_reg_write(dev, L502_REGS_DMA_RST,  1<<ch);

    f_dma_buf_param_init(dev, ch, dev->dma[ch].pcbuf_params);

    //dev->dma[ch].cur_transf = 0;
    //dev->dma[ch].start_transf = 0;
    dev->dma[ch].done_page = 0;
    dev->dma[ch].start_page = 0;
    dev->dma[ch].start_transf_id = dev->dma[ch].init_transf_id = dev->dma[ch].done_transf_id = 0;

    return STATUS_SUCCESS;
}




/* Инициализация структур, отвечающих за потоки, в конетексет устройства */
t_status lpcie_streams_init(t_lpcie_dev_context* dev) {
    t_status status = STATUS_SUCCESS;
    t_lpcie_dev_cap* cap = NULL;
    unsigned int i;

    /* устанавливаем начальные значения для выходов и ЦАП */
    lpcie_fpga_reg_write(dev, L502_REGS_IOHARD_ASYNC_OUT, 0x00030000);
    lpcie_fpga_reg_write(dev, L502_REGS_IOHARD_ASYNC_OUT, 0x40000000);
    lpcie_fpga_reg_write(dev, L502_REGS_IOHARD_ASYNC_OUT, 0x80000000);

    /* находим структуру с поддерживаемыми DMA-возожностями для нашего устройства */
    for (i=0; (cap==NULL) &&
         (i < sizeof(f_devcap_table)/sizeof(f_devcap_table[0])); i++) {
        if ((dev->vid==f_devcap_table[i].vid) &&
                (dev->pid==f_devcap_table[i].pid)) {
            cap = &f_devcap_table[i];
        }
    }

    if (cap!=NULL) {
        uint32_t i;
        dev->dma_cap = cap->dma_cap;

        for (i=0; (i < dev->dma_cap.ch_cnt) && (IS_SUCCESS(status)); i++) {
#ifdef _WIN32
            uint32_t t;
#endif

            memset(&dev->dma[i], 0, sizeof(dev->dma[i]));
            dev->dma[i].devbuf_params = &dev->dma[i].buf_param[0];
            dev->dma[i].pcbuf_params = &dev->dma[i].buf_param[0];

            f_dma_buf_param_init(dev, i, &dev->dma[i].buf_param[0]);
            f_dma_buf_param_init(dev, i, &dev->dma[i].buf_param[1]);

            dev->dma[i].buf_param[0].state = dev->dma[i].buf_param[1].state
                    = LPCIE_DMA_BUF_STATE_INVALID;
            dev->dma[i].ch = i;
            dev->dma[i].dir = i ? LPCIE_DMA_DIR_OUT : LPCIE_DMA_DIR_IN;

#ifdef _WIN32
            for (t=0; t < LPCIE_MAX_TRANSF_CNT; t++)
                dev->dma[i].transf[t].valid = 0;            
#endif

            status = spinlock_create(dev, &dev->dma[i].param_lock);
            if (IS_SUCCESS(status))
                status = spinlock_create(dev, &dev->dma[i].state_lock);
            if (IS_SUCCESS(status))
                status = dma_state_init(dev, &dev->dma[i]);
        }

        /** @todo освобождение spinlock, если ошибка */
    } else {
        status = STATUS_DEVICE_CONFIGURATION_ERROR;
    }
    return status;
}

/* Функция вызывается, если устройство было закрыто и используется, чтобы
   остановить потоки, если они были запущены и освободить ресурсы на DMA */
void lpcie_streams_dev_release(t_lpcie_dev_context* dev) {
    uint16_t i;
    for (i=0; i < dev->dma_cap.ch_cnt; i++) {
        lpcie_stream_free(dev, i);
        spinlock_delete(dev->dma[i].state_lock);
        spinlock_delete(dev->dma[i].param_lock);
    }
}


t_status lpcie_streams_set_params(t_lpcie_dev_context *dev,
                                   const t_lpcie_stream_ch_params *params,
                                    size_t size) {
    t_status status = params->ch < dev->dma_cap.ch_cnt ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER;
    t_lpcie_dma_state *dma_state;
    if (IS_SUCCESS(status))
        dma_state = &dev->dma[params->ch];


    if (size < sizeof(t_lpcie_stream_ch_params))
        status = STATUS_INVALID_PARAMETER;

    /* проверяем правильность параметров */
    if (IS_SUCCESS(status)) {
        if (params->buf_size==0)
            status = STATUS_INVALID_PARAMETER;
    }

    if (IS_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "Set params req. ch = %d, buf_size = %d, irq_step = %d",
                    params->ch, params->buf_size, params->irq_step);

        /* при запущенном DMA нельзя менять параметры */
        if (LPCIE_DMA_IS_RUNNING(dma_state->pcbuf_params->state)) {
            //status = STATUS_INVALID_DEVICE_STATE;
            //dma_state->pcbuf_params->irq_step = params->irq_step;
        } else {
            /* меняем параметры только если они отличаются от текущих */
            size_t size = ((params->buf_size+LPCIE_PAGE_WRDS_CNT-1)/LPCIE_PAGE_WRDS_CNT);
            size *= LPCIE_PAGE_WRDS_CNT;
            status = f_dma_setup(dev, params->ch, size, params->irq_step,0);
        }
    }
    return status;
}





t_status lpcie_streams_get_params(t_lpcie_dev_context *dev,
                                    uint32_t ch,
                                    t_lpcie_stream_ch_params *params,
                                    size_t *size) {
    t_status status = ch < dev->dma_cap.ch_cnt ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER;
    t_lpcie_dma_state *dma_state;
    if (IS_SUCCESS(status)) {
        dma_state = &dev->dma[ch];
        if (*size < sizeof(t_lpcie_stream_ch_params))
            status = STATUS_INVALID_PARAMETER;
    }

    if (IS_SUCCESS(status)) {
        params->ch = ch;
        params->buf_size = dma_state->pcbuf_params->size;
        params->irq_step = dma_state->pcbuf_params->irq_step;
        *size = sizeof(t_lpcie_stream_ch_params);
    }

    return status;
}


t_status lpcie_stream_get_rdy_size(t_lpcie_dev_context *dev, uint32_t ch, uint32_t *free_size) {
    t_status status = ch < dev->dma_cap.ch_cnt ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER;
    t_lpcie_dma_state *state;
    if (IS_SUCCESS(status)) {
        uint32_t tmp;
        state = &dev->dma[ch];

        spinlock_acquire(state->param_lock);
        tmp = state->pcbuf_params->state == LPCIE_DMA_BUF_STATE_INVALID ? 0 :
              state->pcbuf_params->rdy_size;
        spinlock_release(state->param_lock);
        *free_size = tmp;
    }
    return status;
}




#ifdef _WIN32
/* функция вызываемая после WDF после запуска транзакции для настройки DMA */
BOOLEAN EvtLpcieProgramDma(IN WDFDMATRANSACTION  Transaction,
                           IN WDFDEVICE  Device,
                           IN PVOID  Context,
                           IN WDF_DMA_DIRECTION  Direction,
                           IN PSCATTER_GATHER_LIST  SgList
                           ) {

    t_lpcie_dma_state *state = (t_lpcie_dma_state*)Context;
    t_lpcie_dev_context *dev = LpcieGetDeviceContext(Device);
    uint32_t i;
    int next_trans = 0;
    t_dma_transf *transf = NULL;

    /* определеяем, какой транзакции соответствует этот callback
       по хендлу самой транзакции */
    for (i=0; (transf==NULL) && (i < LPCIE_MAX_TRANSF_CNT); i++) {
        if (state->transf[i].transact==Transaction)
            transf = &state->transf[i];
    }

    if (transf) {
        spinlock_acquire(state->param_lock);

        transf->parts_cnt = (uint16_t)SgList->NumberOfElements;
        transf->proc_size = 0;
        transf->full_size = 0;
        transf->run = 1;


        for (i=0; i < SgList->NumberOfElements; i++) {
            transf->parts[i].bus_addr = SgList->Elements[i].Address.QuadPart;
            transf->parts[i].size = SgList->Elements[i].Length/4;
            transf->full_size += transf->parts[i].size;
        }

        next_trans = f_program_transf(dev, state, transf);

        spinlock_release(state->param_lock);

        if (next_trans)
            f_start_transact(dev, state);
    }

    return TRUE;
}
#endif

/* если запущено меньше транзакций, чем реально можно запустить,
   и, кроме того, имеется готовое для запуска DMA место в буфере хотя бы
   на irq_size отсчетов, то нужно запускать следующую транзацию */
static int f_need_next_transf(t_lpcie_dev_context *dev,  t_lpcie_dma_state *state) {
    size_t rdy = state->devbuf_params->size - state->devbuf_params->rdy_size
            - state->devbuf_params->transf_progr_size;

    if (state->devbuf_params->state == LPCIE_DMA_BUF_STATE_SINGLESHOT)
        return state->devbuf_params->transf_progr_size == 0;

    return ((uint32_t)(state->start_transf_id-state->done_transf_id) < LPCIE_MAX_TRANSF_CNT) &&
            //(state->devbuf_params->state==LPCIE_DMA_BUF_STATE_RUN_CYCLE) ||
            (LPCIE_DMA_IS_RUNNING(state->devbuf_params->state)
            && ((rdy >= state->devbuf_params->irq_step) || ((rdy>0) && (state->devbuf_params->transf_progr_size==0))));//= state->devbuf_params->irq_step);
}


/* Программируем устройство на запуск одной транзации dma */
static int f_program_transf(t_lpcie_dev_context* dev,  t_lpcie_dma_state *state, t_dma_transf *transf) {
    uint32_t i;
    uint32_t ch = state->ch;

    /* записываем адреса страниц в устройство */
    for (i=0; i < transf->parts_cnt; i++) {
        lpcie_fpga_reg_write(dev, L502_REGS_DMA_CH_PAGE_ADDRL(ch, state->start_page),
                             transf->parts[i].bus_addr & 0xFFFFFFFF);
        /* если bus_addr 32-битный, то сдвиг на 32 -> undefined behavior =>
           обрабатываем ситуацию явно */
        lpcie_fpga_reg_write(dev, L502_REGS_DMA_CH_PAGE_ADDRH(ch, state->start_page),
                             (sizeof(transf->parts[i].bus_addr) > 4) ?
                                 ((transf->parts[i].bus_addr>>32) & 0xFFFFFFFF) : 0);
        lpcie_fpga_reg_write(dev, L502_REGS_DMA_CH_PAGE_LEN(ch, state->start_page),
                             (i==(transf->parts_cnt-1) ? 0x80000000 : 0)
                             | transf->parts[i].size);
        if (++state->start_page==dev->dma_cap.max_pages_cnt)
            state->start_page = 0;
    }





    /* обновляем позицию в буфере */
    transf->buf->transf_pos += transf->full_size;
    if (transf->buf->transf_pos>=transf->buf->size)
        transf->buf->transf_pos -= transf->buf->size;
    transf->buf->transf_progr_size += transf->full_size;

    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA,
       "dma program (ch=%d). parts cnt = %d, full_size = %d, pos = %d, progr_size = %d",
        state->ch,
        transf->parts_cnt,
        transf->full_size,
        transf->buf->transf_pos,
        transf->buf->transf_progr_size);

    state->init_transf_id++;


    /* Записываем номер страницы в устройстве, до которой можно
        выполнять DMA */
    lpcie_fpga_reg_write(dev, L502_REGS_DMA_CH_PC_POS(state->ch),
                         state->start_page  << L502_REGBIT_DMA_CURPOS_PAGE_Pos);

    return f_need_next_transf(dev, state);
}


/* запуск транзакции DMA на часть буфера */
static t_status f_start_transact(t_lpcie_dev_context* dev, t_lpcie_dma_state* state) {
    t_status status = STATUS_SUCCESS;
    t_dma_transf* transf = &state->transf[LPCIE_TRANS_IND(state->start_transf_id)];
    size_t trans_size;

    if (state->init_transf_id!=state->start_transf_id)
        return STATUS_SUCCESS;


    transf->buf = state->devbuf_params;

    trans_size = transf->buf->size - transf->buf->rdy_size
                             - transf->buf->transf_progr_size;
    if (trans_size > transf->buf->irq_step)
        trans_size = transf->buf->irq_step;

 #ifdef _WIN32
    if (!transf->valid) {
        status = WdfDmaTransactionCreate(dev->dmaEnabler[LPCIE_TRANS_IND(state->start_transf_id)],
                                         WDF_NO_OBJECT_ATTRIBUTES,
                                        &transf->transact);
        if (!IS_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA,
                       "transaction create error!: %!STATUS!",  status);
        } else {
            transf->valid = 1;
        }
    }

    if (IS_SUCCESS(status)) {
        spinlock_acquire(state->param_lock);
        if (trans_size > (transf->buf->size - transf->buf->transf_pos))
            trans_size = transf->buf->size - transf->buf->transf_pos;
        if (trans_size && f_need_next_transf(dev, state)) {
            state->start_transf_id++;
            spinlock_release(state->param_lock);


            status = WdfDmaTransactionInitialize(transf->transact,
                                                 EvtLpcieProgramDma,
                                                 state->dir,
                                                 transf->buf->mdl,
                                                 &transf->buf->buf[transf->buf->transf_pos],
                                                 trans_size*4);
           if (!IS_SUCCESS(status)) {
               TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA,
                          "transaction initialize witch size =%d error!: %!STATUS!",
                           trans_size, status);
           } else {
               status = WdfDmaTransactionExecute(transf->transact, state);
               if (!IS_SUCCESS(status)) {
                   TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA,
                              "transaction execute error!: %!STATUS!",  status);
                   state->start_transf_id--;
               }
           }
        } else {
            spinlock_release(state->param_lock);
        }
    }
#else
    if (IS_SUCCESS(status)) {
        int next_trans=0;
        uint32_t offs, page;

        spinlock_acquire(state->param_lock);

        /** @note Хотя теоретически мы можем запускать передачу так, чтобы
            часть страниц была из конца буфера, часть из начала
            (за исключением ожидания конца буфера), но тут еще возникает
            момент, что если буфер не кратен старанице, то это нужно
            дополнительно учитывать, что в середине трасфера может быть
            укороченная проблема. В общем это создает больше сложностей,
            чем добавляет преимуществ, поэтому мы явно указываем, что
            не можем переходить через конец буфера */
        if (trans_size > (transf->buf->size - transf->buf->transf_pos)) {
            trans_size = transf->buf->size - transf->buf->transf_pos;
        }



        if (trans_size && f_need_next_transf(dev, state)) {

            state->start_transf_id++;

            transf->full_size = 0;
            transf->parts_cnt = 0;
            page = transf->buf->transf_pos >> LPCIE_PAGE_WRDS_SHIFT;
            offs = transf->buf->transf_pos & ((1<<LPCIE_PAGE_WRDS_SHIFT)-1);

            while ((trans_size != 0) && IS_SUCCESS(status)) {
                t_lpcie_dma_part *part = &transf->parts[transf->parts_cnt];
                part->size = LPCIE_PAGE_WRDS_CNT - offs;
                if (part->size  > trans_size)
                    part->size  = trans_size;

                part->page = transf->buf->pages[page].page;
                part->bus_addr = dma_map_page(&dev->pci_dev->dev, part->page, offs*4, part->size*4,
                                              state->dir);
                if (dma_mapping_error(&dev->pci_dev->dev, part->bus_addr) != 0) {
                    status = STATUS_INSUFFICIENT_RESOURCES;
                    TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA, "dma map page (offs %d, size %d) error!",
                                offs, part->size);
                } else {
                    transf->parts_cnt++;
                    trans_size-= part->size;
                    transf->full_size += part->size;

                    /* недопустимо превысить указанное кол-во передач */
                    if (transf->parts_cnt == LPCIE_MAX_PARTS_PER_TRANSF)
                        trans_size = 0;

                    /* если еще осталось место - переходим к следующей странице */
                    if (trans_size != 0) {
                        if (++page==transf->buf->pages_cnt)
                            page=0;
                        offs = 0;
                    }
                }
            }

            if (IS_SUCCESS(status)) {
                transf->run = 1;

                next_trans = f_program_transf(dev, state, transf);
            } else {
                state->start_transf_id--;
                while (transf->parts_cnt != 0) {
                    t_lpcie_dma_part *part = &transf->parts[transf->parts_cnt - 1];
                    dma_unmap_page(&dev->pci_dev->dev, part->bus_addr,
                                   part->size*4, state->dir);
                }
            }
        }

        spinlock_release(state->param_lock);

        if (next_trans)
            f_start_transact(dev, state);

    }
#endif
    return status;
}


t_status lpcie_stream_start(t_lpcie_dev_context *dev, uint32_t ch, int single) {
    t_status status = ch < dev->dma_cap.ch_cnt ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER;
    t_lpcie_dma_state *dma_state;
    if (IS_SUCCESS(status)) {
        dma_state = &dev->dma[ch];

        /* проверяем, что DMA по этому каналу не запущен */
        if (LPCIE_DMA_IS_RUNNING(dma_state->pcbuf_params->state))
            status = STATUS_INVALID_DEVICE_STATE;
    }


    /* сбрасываем состояние dma-канала */
    if (IS_SUCCESS(status))
        status = f_dma_rst(dev, ch);

    if (IS_SUCCESS(status) && (dma_state->pcbuf_params->state==LPCIE_DMA_BUF_STATE_INVALID)) {
        status = f_dma_setup(dev, ch, dma_state->pcbuf_params->size,
                             dma_state->pcbuf_params->irq_step, 0);
    }

    if (IS_SUCCESS(status)) {
        spinlock_acquire(dma_state->state_lock);
        dma_state->pcbuf_params->state = single ? LPCIE_DMA_BUF_STATE_SINGLESHOT : LPCIE_DMA_BUF_STATE_RUN_STREAM;

        /* запускаем транзакцию на обмен */
        if (f_need_next_transf(dev, dma_state))
            status = f_start_transact(dev, dma_state);
        spinlock_release(dma_state->state_lock);

        if (IS_SUCCESS(status)) {
            /* разрешаем прерывания */
            lpcie_fpga_reg_write(dev, L502_REGS_DMA_IRQ_EN, L502_REGBIT_DMA_IRQ_PAGE_Msk(ch) |
                                 ((dev->dma[ch].dir == LPCIE_DMA_DIR_IN) ? L502_REGBIT_DMA_IRQ_FLUSH_Msk(ch) : 0));
            lpcie_fpga_reg_write(dev, L502_REGS_DMA_EN, (1<<ch));
        } else {
            f_dma_stop(dev, ch);
        }
    }
    return status;
}




t_status lpcie_stream_stop(t_lpcie_dev_context *dev, uint32_t ch) {
    t_status status = ch < dev->dma_cap.ch_cnt ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER;
    if (IS_SUCCESS(status)) {
        status = f_dma_stop(dev, ch);
    }
    return status;
}

t_status lpcie_stream_free(t_lpcie_dev_context *dev, uint32_t ch) {
    t_status status = ch < dev->dma_cap.ch_cnt ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER;
    if (IS_SUCCESS(status)) {
        f_dma_stop(dev, ch);
        f_dma_free(dev, dev->dma[ch].devbuf_params);
        f_dma_free(dev, dev->dma[ch].pcbuf_params);
        dev->dma[ch].devbuf_params = dev->dma[ch].pcbuf_params = &dev->dma[ch].buf_param[0];
    }
    return status;
}

static t_lpcie_dma_buf_param *f_next_buf_params(t_lpcie_dma_buf_param *param, t_lpcie_dma_state *state) {
    return param == &state->buf_param[0] ? &state->buf_param[1] : &state->buf_param[0];
}


t_status lpcie_stream_cycle_load(t_lpcie_dev_context *dev, const t_lpcie_cycle_set_par *params) {
    uint32_t done;
    t_status status = lpcie_stream_cycle_setup_done(dev, params->ch, &done);
    t_lpcie_dma_state *state = NULL;

    if (IS_SUCCESS(status) && !done) {
        status = STATUS_INVALID_DEVICE_STATE;
    }

    if (IS_SUCCESS(status)) {
        state = &dev->dma[params->ch];

        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "start load. pcbuf_state = %d", state->pcbuf_params->state);

        if (state->pcbuf_params==state->devbuf_params) {
            if (state->pcbuf_params->state == LPCIE_DMA_BUF_STATE_RUN_CYCLE) {
                TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "switch pc buffer!");
                state->pcbuf_params=f_next_buf_params(state->pcbuf_params, state);
                f_dma_buf_param_init(dev, params->ch, state->pcbuf_params);
            } else if (!LPCIE_DMA_IS_RUNNING(state->pcbuf_params->state)) {
                f_dma_rst(dev, params->ch);
            }
        } else {
            status = STATUS_INVALID_DEVICE_STATE;
        }
    }

    if (IS_SUCCESS(status)) {
        uint32_t size = params->size;
        /* если размер меньше минимального, то дополняем его до нужного, иначе
         * слишком часто придется перенастраивать DMA и будет слишком большая нагрузка
         * если шаг прерывания задан явно, то это и будет минимальный размер */
        uint32_t min_size = params->irq_step == 0 ? LPCIE_CYCLE_BUF_SIZE_MIN : params->irq_step;
        uint32_t irq_step = size;

        if (size < min_size) {
            state->pcbuf_params->mul = (min_size+size-1)/size;
            size *= state->pcbuf_params->mul;
            irq_step = size;
            TraceEvents(TRACE_LEVEL_VERBOSE,  DBG_DMA, "Use cycle buffer multipler = %d\n",
                        state->pcbuf_params->mul);
        } else {
            /* если размер буфера больше заданного шага прерывания, то шаг прерывания
               устанавливаем как кратный размеру буфера и ближайший ниже заданного */
            uint32_t irq_cnt = (size + irq_step - 1)/irq_step;
            irq_step = size/irq_cnt;
            state->pcbuf_params->mul = 1;

        }

        status = f_dma_setup(dev, params->ch, size, irq_step, 0);

        state->pcbuf_params->rdy_size = params->size;



        if (IS_SUCCESS(status)) {
            state->pcbuf_params->state = LPCIE_DMA_BUF_STATE_LOAD_CYCLE;
        }
    }

    return status;
}



/* копируем внутри буфера DMA  */
static t_status f_copy_buf_data(t_lpcie_dev_context *dev, uint32_t ch,
                                uint32_t offset, size_t size) {
    t_lpcie_dma_state *state = &dev->dma[ch];
    t_status status = STATUS_SUCCESS;
#ifdef _WIN32
    memcpy(&state->pcbuf_params->buf[offset], state->pcbuf_params->buf, size*4);
#else
    uint32_t src_page=0, src_offs=0, dst_page, dst_offs;

    dst_page = offset>> LPCIE_PAGE_WRDS_SHIFT;
    dst_offs = offset & ((1<<LPCIE_PAGE_WRDS_SHIFT)-1);

    while (size && IS_SUCCESS(status)) {
        uint32_t cpy_size = size;
        char *src_addr = (char*)kmap(state->pcbuf_params->pages[src_page].page);
        char *dst_addr = (char*)kmap(state->pcbuf_params->pages[dst_page].page);

        if (src_addr && dst_addr) {
            src_addr+=src_offs*4;
            dst_addr+=dst_offs*4;

            if (cpy_size > (LPCIE_PAGE_WRDS_CNT-src_offs))
                cpy_size = LPCIE_PAGE_WRDS_CNT-src_offs;
            if (cpy_size > (LPCIE_PAGE_WRDS_CNT-dst_offs))
                cpy_size = LPCIE_PAGE_WRDS_CNT-dst_offs;

            memcpy(dst_addr, src_addr, cpy_size*4);

            kunmap(state->pcbuf_params->pages[src_page].page);
            kunmap(state->pcbuf_params->pages[dst_page].page);

            size-=cpy_size;
            /* если еще осталось место - переходим к следующей странице */
            if (size) {
                src_offs+=cpy_size;
                dst_offs+=cpy_size;

                if (src_offs>=LPCIE_PAGE_WRDS_CNT) {
                    src_offs-=LPCIE_PAGE_WRDS_CNT;
                    src_page++;
                }

                if (dst_offs>=LPCIE_PAGE_WRDS_CNT) {
                    dst_offs-=LPCIE_PAGE_WRDS_CNT;
                    dst_page++;
                }
            }
        } else {
            status = STATUS_INSUFFICIENT_RESOURCES;
            TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA, "pages map error!");
            if (src_addr)
                kunmap(state->pcbuf_params->pages[src_page].page);
        }
    }
#endif
    return status;
}

t_status lpcie_stream_cycle_switch(t_lpcie_dev_context *dev, const t_lpcie_cycle_evt_par* params) {
    t_status status = params->ch < dev->dma_cap.ch_cnt ? dev->dma[params->ch].dir==LPCIE_DMA_DIR_OUT ?
                    STATUS_SUCCESS :  STATUS_INVALID_PARAMETER : STATUS_INVALID_PARAMETER;
    t_lpcie_dma_state* dma_state;
    if (IS_SUCCESS(status)) {
        dma_state = &dev->dma[params->ch];
        if (dma_state->pcbuf_params->state != LPCIE_DMA_BUF_STATE_LOAD_CYCLE) {
            TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA, "cannot switch buf, invalid buf state=%d",
                        dma_state->pcbuf_params->state);
            status = STATUS_INVALID_DEVICE_STATE;
        } else {
            uint32_t unload_size = dma_state->pcbuf_params->rdy_size;

            if (unload_size != 0) {
                TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA, "cannot switch buf, all data didn't loaded. You need load %d samples",
                            unload_size);
                status = STATUS_INVALID_DEVICE_STATE;
            }
        }
    }

    if (IS_SUCCESS(status)) {
        /* если буфер был увеличен, то копируем копии данных в расширенную часть */
        if (dma_state->pcbuf_params->mul > 1) {
            unsigned i;
            size_t part_size = dma_state->pcbuf_params->size/dma_state->pcbuf_params->mul;
            for (i=1; (i < dma_state->pcbuf_params->mul) && (IS_SUCCESS(status)); i++) {
                status = f_copy_buf_data(dev, params->ch, part_size*i, part_size);
            }
        }
    }

    if (IS_SUCCESS(status)) {
        spinlock_acquire(dma_state->state_lock);

        /* если до этого не было запуска - запускаем циклический сбор */
        if (!LPCIE_DMA_IS_RUNNING(dma_state->devbuf_params->state)) {
            dma_state->pcbuf_params->state = LPCIE_DMA_BUF_STATE_RUN_CYCLE;
            /* запускаем транзакцию на обмен */
            if (f_need_next_transf(dev, dma_state))
                status = f_start_transact(dev, dma_state);

            if (IS_SUCCESS(status)) {
                /* разрешаем прерывания */
                lpcie_fpga_reg_write(dev, L502_REGS_DMA_IRQ_EN, L502_REGBIT_DMA_IRQ_PAGE_Msk(params->ch));
                lpcie_fpga_reg_write(dev, L502_REGS_DMA_EN, (1<<params->ch));
            }
        } else {
            dma_state->devbuf_params->sw_evt = params->evt;
            dma_state->devbuf_params->evt_type = LPCIE_EVT_TYPE_NEXT_BUF;
            dma_state->pcbuf_params->state = LPCIE_DMA_BUF_STATE_RUN_CYCLE;
            TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "switch dev buffer request!");
        }
        spinlock_release(dma_state->state_lock);

        if (!IS_SUCCESS(status)) {
            f_dma_stop(dev, params->ch);
        }
    }

    return status;
}

t_status lpcie_stream_cycle_stop(t_lpcie_dev_context *dev, const t_lpcie_cycle_evt_par* params) {
    t_status status = params->ch < dev->dma_cap.ch_cnt ? dev->dma[params->ch].dir==LPCIE_DMA_DIR_OUT ?
                    STATUS_SUCCESS :  STATUS_INVALID_PARAMETER : STATUS_INVALID_PARAMETER;
    t_lpcie_dma_state* dma_state;
    if (IS_SUCCESS(status)) {
        dma_state = &dev->dma[params->ch];
        if (dma_state->pcbuf_params->state!=LPCIE_DMA_BUF_STATE_RUN_CYCLE)  {
            status = STATUS_INVALID_DEVICE_STATE;
        } else {
            dma_state->pcbuf_params->sw_evt = params->evt;
            dma_state->pcbuf_params->evt_type = LPCIE_EVT_TYPE_STOP;
        }
    }
    return status;
}

t_status lpcie_stream_cycle_setup_done(t_lpcie_dev_context *dev, uint32_t ch, uint32_t *done) {
    t_status status = ch < dev->dma_cap.ch_cnt ? dev->dma[ch].dir==LPCIE_DMA_DIR_OUT ?
                    STATUS_SUCCESS :  STATUS_INVALID_PARAMETER : STATUS_INVALID_PARAMETER;
    t_lpcie_dma_state* dma_state;
    if (IS_SUCCESS(status)) {
        dma_state = &dev->dma[ch];
        if (dma_state->pcbuf_params == dma_state->devbuf_params) {
            *done = (dma_state->pcbuf_params->evt_type == LPCIE_EVT_TYPE_NONE) &&
                    (f_next_buf_params(dma_state->pcbuf_params, dma_state)->evt_type == LPCIE_EVT_TYPE_NONE);
        } else {
            *done = 0;
        }
    }
    return status;
}

/* копируем данные из буфера DMA в пользовательский буфер */
static t_status f_copy_data(t_lpcie_dev_context *dev, uint32_t ch,
                            t_lpcie_req_context* req, size_t size) {
    t_lpcie_dma_state *state = &dev->dma[ch];
    t_status status = STATUS_SUCCESS;
#ifdef _WIN32
    if (state->dir == LPCIE_DMA_DIR_IN) {
        WdfMemoryCopyFromBuffer(req->buf, req->proc_size*4,
                                &state->pcbuf_params->buf[state->pcbuf_params->pc_pos],
                                size*4);
    } else {
        WdfMemoryCopyToBuffer(req->buf, req->proc_size*4,
                              &state->pcbuf_params->buf[state->pcbuf_params->pc_pos],
                              size*4);
    }
#else
    uint32_t page, offs;
    uint32_t* wrd_buf = (uint32_t*)req->addr;
    wrd_buf += req->proc_size;

    page = state->pcbuf_params->pc_pos >> LPCIE_PAGE_WRDS_SHIFT;
    offs = state->pcbuf_params->pc_pos & ((1<<LPCIE_PAGE_WRDS_SHIFT)-1);

    PDEBUG("transf page = %d, offs = %d", page, offs);

    while (size && IS_SUCCESS(status)) {
        uint32_t cpy_size = size;
        char* addr = (char*)kmap(state->pcbuf_params->pages[page].page);

        if (addr) {
            addr+=offs*4;
            if (cpy_size > (LPCIE_PAGE_WRDS_CNT-offs))
                cpy_size = LPCIE_PAGE_WRDS_CNT-offs;
            if (state->dir == LPCIE_DMA_DIR_IN) {
                status = copy_to_user(wrd_buf, addr, cpy_size*4);
            } else {
                status = copy_from_user(addr, wrd_buf, cpy_size*4);
            }

            kunmap(state->pcbuf_params->pages[page].page);
        }

        if (IS_SUCCESS(status)) {

            size-=cpy_size;
            /* если еще осталось место - переходим к следующей странице */
            if (size) {
                wrd_buf += cpy_size;
                if (++page==state->pcbuf_params->pages_cnt)
                    page=0;
                offs = 0;
            }
        }
    }
#endif
    return status;
}

t_status lpcie_proc_req(t_lpcie_dev_context *dev, t_lpcie_dma_state* state, t_lpcie_req_context* req) {
    int again;
    t_status status = STATUS_SUCCESS;

    do {
        /* смотрим, сколько доступно для приема/передачи и сколько
         * еще осталось передать по запросу */
        uint32_t count = req->req_size - req->proc_size;
        uint32_t rdy = state->pcbuf_params->state == LPCIE_DMA_BUF_STATE_INVALID ?
                    0 : state->pcbuf_params->rdy_size;

        again = 0;

        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "Request (ch=%d) for %d, rdy = %d",
                    state->ch, count, rdy);

        if (count && rdy) {
            if (count > rdy)
                count = rdy;

            /* если находимся в конце буфера, то передачу разбиваем на
             * 2 части - до конца и потом с начала буфера */
            if (count > (state->pcbuf_params->size - state->pcbuf_params->pc_pos)) {
                count = state->pcbuf_params->size - state->pcbuf_params->pc_pos;
                again = 1;
            }

            /* копируем данные */
            f_copy_data(dev, state->ch, req, count);



            spinlock_acquire(state->state_lock);
            spinlock_acquire(state->param_lock);

            state->pcbuf_params->pc_pos += count;
            state->pcbuf_params->rdy_size -= count;
            req->proc_size+=count;


            if (state->pcbuf_params->pc_pos==state->pcbuf_params->size)
                state->pcbuf_params->pc_pos = 0;


            spinlock_release(state->param_lock);

            /* если освободили место для запуска новой транзакции,
               для которой не было место в буфере - запускаем ее */
            if (f_need_next_transf(dev, state))
                f_start_transact(dev, state);

            spinlock_release(state->state_lock);


        }
    }
    while (again);

    return status;
}

#ifdef _WIN32
void lpcie_streams_proc_transf(t_lpcie_dev_context *dev, uint32_t ch) {
    t_status cpl_status = STATUS_SUCCESS;
    t_lpcie_dma_state *state = &dev->dma[ch];
    int unlocked = 0;

    if (!state->req_valid)
        return;

    WdfWaitLockAcquire(state->work_lock, NULL);

    if (state->req_valid) {
        t_lpcie_req_context *context = LpcieGetReqContext(state->req);
        lpcie_proc_req(dev, state, context);

        if (context->proc_size == context->req_size) {
            t_status cancel_status;

            /* перед тем как завершить запрос, снимем признак, что его можно
              отменить для избежания броблемы одновременной отмены этого же запроса */
            cancel_status = WdfRequestUnmarkCancelable(state->req);
            if (cancel_status!=STATUS_CANCELLED) {
                TraceEvents(TRACE_LEVEL_WARNING, DBG_TRANSF,
                            "full request completed (ch=%d) with size. = %d", ch, context->proc_size*4);
                state->req_valid = 0;
                unlocked = 1;
                /* Так как из WdfRequestCompleteWithInformation() мжет быть сразу
                 * вызвана обработка следующего запроса, то необходимо осовободить
                 * блокировку до вызова WdfRequestCompleteWithInformation(), чтобы
                 * не попасть в deadlock */
                WdfWaitLockRelease(state->work_lock);
                WdfRequestCompleteWithInformation(state->req, cpl_status, context->proc_size*4);
            }
            TraceEvents(TRACE_LEVEL_WARNING, DBG_TRANSF, "done unmark cancel");

        }
    }

    if (!unlocked)
        WdfWaitLockRelease(state->work_lock);
}

#endif


static t_status f_proc_transf_done(t_lpcie_dev_context* dev, t_lpcie_dma_state* state,
                               t_dma_transf* transf, int *req_stop) {
    t_status status = STATUS_SUCCESS;
    int free_buf = 0;
    spinlock_acquire(state->param_lock);
    state->done_page += transf->parts_cnt;
    if (state->done_page>=dev->dma_cap.max_pages_cnt)
        state->done_page -= dev->dma_cap.max_pages_cnt;

    transf->buf->transf_progr_size -= transf->full_size;

    /* если нам нужно переключить используемый буфер */
    if (transf->buf->evt_type != 0) {
        /* если переключение еще не начиналось - то выполняем его */
        if (transf->buf == state->devbuf_params) {
            if ((transf->buf->sw_evt==LPCIE_CYCLE_SW_EVT_IMMIDIATLY) ||
                 ((transf->buf->sw_evt==LPCIE_CYCLE_SW_EVT_END_OF_CYCLE) && (transf->buf->transf_pos==0))) {
                if (transf->buf->evt_type==LPCIE_EVT_TYPE_NEXT_BUF) {
                    state->devbuf_params = f_next_buf_params(state->devbuf_params, state);
                    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "switch dev buffer!");
                } else if (transf->buf->evt_type==LPCIE_EVT_TYPE_STOP) {
                    state->devbuf_params->state = LPCIE_DMA_BUF_STATE_ALLOCATED;
                    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "find stop request size = %d",
                                transf->buf->transf_progr_size);
                    /* завершилась выдача буфера при запросе на останов => можно его освободить */
                    if (transf->buf->transf_progr_size==0)
                        free_buf = 1;                    
                }
            }
        } else {
            TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "switch in progress = %d",
                        transf->buf->transf_progr_size);
            /* уже произошло переключение (транзакции запускаются для нового
               буфера, но по этому еще не завершены) => отслеживаем, когда
               буфер можно будет освободить */
            if (transf->buf->transf_progr_size==0)
                free_buf = 1;
        }
    }

    state->done_transf_id++;

    spinlock_release(state->param_lock);

    if (free_buf) {
#ifdef  _WIN32
        state->buf_to_free = transf->buf;
#else
        f_dma_free(dev, transf->buf);
#endif
        /** @note Не можем использовать останов в данном месте, т.к.
         * при нем выполняется сброс буфера в модулей, а не все данные из
         * этого буфера уже успели передаться. Поэтому только очищаем буфер в ПК,
         * а останов DMA будет при общем останове потоков */
#if 0
        if (transf->buf->evt_type==LPCIE_EVT_TYPE_STOP){
            *req_stop = 1;
        }
#endif

    }

    if (f_need_next_transf(dev, state))
        status = f_start_transact(dev, state);

    return status;
}

/* Обработка прерываний от устройства
 * (подразумевается что сами признаки прерываний уже были сброшены) */
void lpcie_streams_proc_irq(t_lpcie_dev_context *dev, uint32_t irq_state) {
    uint32_t ch;
    /* проверяем, по какому каналу были прерывания */
    for (ch=0; ch  < dev->dma_cap.ch_cnt; ch++) {
        t_lpcie_dma_state* state = &dev->dma[ch];
        int work_start = 0; /* требуется ли обработка новых принятых данных */


        /* завершена полностью часть трансфера */
        if (irq_state & (L502_REGBIT_DMA_IRQ_PAGE_Msk(ch) | L502_REGBIT_DMA_IRQ_FLUSH_Msk(ch))) {
            uint32_t pages = 0, offset;
            t_dma_transf* transf;
            int req_stop = 0;

            spinlock_acquire(state->state_lock);

            /* получаем страницу, которая завершилась */
            lpcie_fpga_reg_read(dev, L502_REGS_DMA_CH_CUR_POS(ch), &pages);
            offset = (pages & L502_REGBIT_DMA_CURPOS_OFFSET_Msk) >> L502_REGBIT_DMA_CURPOS_OFFSET_Pos;
            pages =  (pages & L502_REGBIT_DMA_CURPOS_PAGE_Msk) >> L502_REGBIT_DMA_CURPOS_PAGE_Pos;

            TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "irq proc: pages = %d, offset = %d, done pages = %d",
                                                            pages, offset, state->done_page);

            /* получаем сколько страниц завершено
               Здесь есть тонкий момент: если номер завершенной равен текущему,
               то считаем что ничего не завершено (может быть, если 2 прерывания
               идут подряд и во втором обработали первое). Предпологается что
               задержки такой, что весь буфер не будет обработан не должно быть */
            pages = pages >= state->done_page ? pages - state->done_page :
                                             dev->dma_cap.max_pages_cnt + pages - state->done_page;

            transf = &state->transf[LPCIE_TRANS_IND(state->done_transf_id)];



            while ((transf->parts_cnt <= pages) && !req_stop && transf->run) {
                t_status status = STATUS_SUCCESS;                

                pages -= transf->parts_cnt;

                /* увеличиваем размер готовой для работы со стороны PC части данных */
                spinlock_acquire(state->param_lock);
                if (transf->buf->state!=LPCIE_DMA_BUF_STATE_RUN_CYCLE)
                    transf->buf->rdy_size += transf->full_size;
                spinlock_release(state->param_lock);

                /* завершаем трансфер */

#ifdef _WIN32
                if (transf->run) {
                    WdfDmaTransactionDmaCompletedFinal(transf->transact,
                                                       transf->full_size*4, &status);
                    if (IS_SUCCESS(status)) {
                        transf->run = 0;
                        /* вся передача завершена => ставим новую */
                        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "Full transaction completed");
                        status = WdfDmaTransactionRelease(transf->transact);
                        if (!IS_SUCCESS(status)) {
                            TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA, "transaction release error!: %!STATUS!", status);
                            req_stop = 1;
                        } else {
                            status = f_proc_transf_done(dev, state, transf, &req_stop);
                            work_start = 1;
                        }
                    } else {
                        /* ошибка завершения транзакции... */
                        TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA,
                                    "Complete transaction error!: %!STATUS!",  status);
                        req_stop = 1;
                    }
                }
#else

                /* по завершению транзакции освобождаем все буфера, чтобы можно
                   было использовать эти страницы из ПК */
                f_release_transf(dev, state, transf);

                status = f_proc_transf_done(dev, state, transf, &req_stop);
                work_start = 1;
#endif
                if (transf->buf->state == LPCIE_DMA_BUF_STATE_SINGLESHOT)
                    req_stop = 1;


                transf = &state->transf[LPCIE_TRANS_IND(state->done_transf_id)];

                if (transf->parts_cnt <= pages) {
                    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "more that 2 transf! req=%d, pages = %d",
                                transf->parts_cnt, pages);
                }
            }


            if (irq_state & L502_REGBIT_DMA_IRQ_FLUSH_Msk(ch)) {
                TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "blackfin flush event!");

                /* если осталась незавершенная транзакция => завершаем ее */
                if (transf->run) {
                    int out = 0;

                    t_status status = STATUS_SUCCESS;
                    uint32_t proc_size=0, cur_page=0;
                    while (pages) {
                        proc_size += transf->parts[cur_page].size;
                        cur_page++;
                        pages--;
                    }
                    proc_size+=offset;

                    spinlock_acquire(state->param_lock);
                    transf->buf->rdy_size += proc_size;
                    spinlock_release(state->param_lock);
#ifdef _WIN32
                    WdfDmaTransactionDmaCompletedFinal(transf->transact,
                                                       proc_size*4, &status);
                    if (IS_SUCCESS(status)) {
                        transf->run = 0;
                        /* вся передача завершена => ставим новую */
                        TraceEvents(TRACE_LEVEL_VERBOSE, DBG_DMA, "Full transaction completed");
                        status = WdfDmaTransactionRelease(transf->transact);
                        if (!IS_SUCCESS(status)) {
                            TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA, "transaction release error!: %!STATUS!", status);
                            req_stop = 1;
                        } else {
                            status = f_proc_transf_done(dev, state, transf, &req_stop);
                            work_start = 1;
                        }
                    } else {
                        /* ошибка завершения транзакции... */
                        TraceEvents(TRACE_LEVEL_ERROR, DBG_DMA,
                                    "Complete transaction error!: %!STATUS!",  status);
                        req_stop = 1;
                    }
#else
                    //spinlock_acquire(state->state_lock);
                    f_release_transf(dev, state, transf);
                    status = f_proc_transf_done(dev, state, transf, &req_stop);
#endif
                    do {
                        out = 0;
                        transf = &state->transf[LPCIE_TRANS_IND(state->done_transf_id)];
                        if (state->done_transf_id == state->start_transf_id)
                            out = 1;
                        if (transf->run) {
                            f_release_transf(dev, state, transf);
                            state->done_transf_id++;
                        }
                    } while (!out);

                    state->init_transf_id = state->start_transf_id;
                    state->start_page = state->done_page=0;

                    if (state->devbuf_params->transf_pos > state->devbuf_params->transf_progr_size) {
                        state->devbuf_params->transf_pos -= state->devbuf_params->transf_progr_size;
                    } else {
                        state->devbuf_params->transf_pos = state->devbuf_params->size -
                                state->devbuf_params->transf_progr_size + state->devbuf_params->transf_pos;
                    }
                    state->devbuf_params->transf_progr_size = 0;

                    lpcie_fpga_reg_write(dev, L502_REGS_DMA_CH_PC_POS(ch), 0);
                    lpcie_fpga_reg_write(dev, L502_REGS_DMA_CH_CUR_POS(ch),0);

                    /* перезапускаем DMA */
                    f_start_transact(dev, state);
                    lpcie_fpga_reg_write(dev, L502_REGS_DMA_EN, (1<<ch));
                    //req_stop = 1;
                }

                work_start = 1;
            }

            spinlock_release(state->state_lock);

            if (req_stop)
                f_dma_stop(dev, ch);
        }



#ifdef _WIN32
        /* если были переданы новые данные, то ставим в очередь процедуру
         * для переноса этих данных в пользовательский буфер */
        if ((work_start && state->req_valid) || (state->buf_to_free)) {
            LONG val;
            /* сбрасываем признак, что WorkItem был запущен */
            val = InterlockedCompareExchange(&state->work_enqeued, 1, 0);
            if (val==0) {
                WdfWorkItemEnqueue(state->work);
            }
        }
#else
        if (work_start) {
            /* если кото-то ждет данных то пробуждаем его */
            wake_up_interruptible(&state->queue);
        }
#endif
    }
}
