#ifdef ENABLE_USB
#include "libusb-1.0/libusb.h"
#include "e502api_private.h"
#include "lboot_req.h"
#include "e502_fpga_regs.h"
#include "osspec.h"
#include "ltimer.h"

#include <stdlib.h>
#include <string.h>

#define E502_USB_VID        0x2A52
#define E502_USB_PID        0xE502
#define E502_USB_REQ_TOUT   3000

#define E440_USB_VID        0x0471
#define E440_USB_PID        0x0440

#define E16_USB_VID        0x2A52
#define E16_USB_PID        0x0E16

#define USB_CTL_REQ_MAX_SIZE 512


#define MUTEX_STREAM_LOCK_TOUT  2000


#define STREAM_STOP_TOUT      1000

#define USB_IN_STREAM_BUF_MIN   64


#define USB_BULK_RX_MAX_TRANSF_CNT          20
#define USB_BULK_RX_MAX_TRANSF_SIZE         (32*1024)
#define USB_BULK_RX_TRANSFER_TOUT           -1

#define USB_BULK_TX_MAX_TRANSF_CNT          20
#define USB_BULK_TX_MAX_TRANSF_SIZE         (32*1024)
#define USB_BULK_TX_TRANSFER_TOUT           -1

#ifndef LIBUSB_CALL
    #define LIBUSB_CALL
#endif

static int f_lusb_init_done = 0;


struct st_transf_info;


typedef enum {
    TRANSF_CPL_BUSY = 0,
    TRANSF_CPL_COMPLETED  = 1,
    TRANSF_CPL_IDLE       = 2
} t_transf_state;

typedef struct {
    uint32_t *addr;
    uint32_t size;
    uint32_t transf_pos;
    volatile enum {
        RX_STATE_IDLE,
        RX_STATE_BUSY,
        RX_STATE_RDY,
        RX_STATE_ERR
    } state;
} t_rx_cpl_part; /* результат выполнения запросов */

typedef struct st_transf_info {
    t_x502_hnd dev;
    uint8_t addr; /* адрес конечной точки */
    uint16_t pkt_size;
    t_mutex mutex;
    t_thread thread;
    t_event  user_wake_evt;
    t_event  usb_wake_evt;

    volatile int stop_req;
    int usb_rdy;
    uint32_t *data; /* промежуточный буфер, используемый во всех запросах */
    unsigned buf_size;


    int err;

    union {
        struct {
            t_rx_cpl_part *cpls;
            uint32_t cpl_cnt;
            uint32_t cpl_get_pos;
            uint32_t transf_size;
            uint32_t buf_get_rdy;
        } rx;
        struct {
            uint32_t buf_pos_put;
            uint32_t buf_put_rdy;
            uint32_t busy_size;
        } tx;
    };
} t_transf_info;


typedef struct {
    libusb_device_handle *devhnd;
    t_transf_info streams[X502_STREAM_CH_CNT];
} t_usb_iface_data;


static int32_t f_ioreq(libusb_device_handle *handle, uint32_t cmd_code, uint32_t param,
                   const void* snd_data, uint32_t snd_size,
                   void* rcv_data, uint32_t recv_size, uint32_t* recvd_size, uint32_t tout);

static int32_t f_iface_free_devinfo_ptr(t_x502_devrec_inptr *devinfo_ptr);
static int32_t f_iface_open(t_x502_hnd hnd, const t_x502_devrec *devrec);
static int32_t f_iface_close(t_x502_hnd hnd);
static int32_t f_iface_stream_cfg(t_x502_hnd hnd, uint32_t ch, t_x502_stream_ch_params *params);
static int32_t f_iface_stream_start(t_x502_hnd hnd, uint32_t ch, uint32_t flags);
static int32_t f_iface_stream_stop(t_x502_hnd hnd, uint32_t ch, uint32_t flags);
static int32_t f_iface_stream_free(t_x502_hnd hnd, uint32_t ch, uint32_t flags);
static int32_t f_iface_stream_read(t_x502_hnd hnd, uint32_t *buf, uint32_t size, uint32_t tout);
static int32_t f_iface_stream_write(t_x502_hnd hnd, const uint32_t *buf, uint32_t size, uint32_t tout) ;
static int32_t f_iface_stream_get_rdy_cnt(t_x502_hnd hnd, uint32_t ch, uint32_t *rdy_cnt);

static int32_t f_iface_gen_ioctl(t_x502_hnd hnd, uint32_t cmd_code, uint32_t param,
                                 const void* snd_data, uint32_t snd_size,
                                 void* rcv_data, uint32_t recv_size, uint32_t* recvd_size,
                                 uint32_t tout);

static const t_x502_dev_iface f_usb_iface = {
    E502_REGS_ARM_HARD_ID,
    USB_IN_STREAM_BUF_MIN,
    USB_CTL_REQ_MAX_SIZE,
    USB_CTL_REQ_MAX_SIZE/4,
    USB_CTL_REQ_MAX_SIZE, //flash rd size
    USB_CTL_REQ_MAX_SIZE, //flash wr size
    f_iface_free_devinfo_ptr,
    f_iface_open,
    f_iface_close,
    e502_iface_fpga_read,
    e502_iface_fpga_write,
    f_iface_stream_cfg,
    f_iface_stream_start,
    f_iface_stream_stop,
    f_iface_stream_free,
    e502_iface_stream_running,
    f_iface_stream_read,
    f_iface_stream_write,
    f_iface_stream_get_rdy_cnt,
    e502_iface_bf_mem_block_rd,
    e502_iface_bf_mem_block_wr,
    e502_iface_bf_firm_load,
    e502_iface_flash_rd,
    e502_iface_flash_wr,
    e502_iface_flash_erase,
    e502_iface_flash_set_prot,
    e502_iface_reload_dev_info,
    e502_iface_cycle_load_start,
    e502_iface_cycle_setup,
    e502_iface_cycle_stop,
    e502_iface_cycle_check_setup,
    e502_iface_fpga_mode_init,
    f_iface_gen_ioctl,
    e502_iface_check_feature
};






static int32_t f_iface_open(t_x502_hnd hnd, const t_x502_devrec *devrec) {
    int32_t err, usberr;
    libusb_device *dev = (libusb_device *)devrec->internal->iface_data;
    libusb_device_handle *devhnd = NULL;


    usberr = libusb_open(dev, &devhnd);
    if (!usberr) {
        usberr = libusb_claim_interface(devhnd, 0);
    }

    err = usberr == LIBUSB_SUCCESS ? X502_ERR_OK :
          usberr == LIBUSB_ERROR_BUSY ? X502_ERR_ALREADY_OPENED :
          usberr == LIBUSB_ERROR_ACCESS ? X502_ERR_DEVICE_ACCESS_DENIED :
          usberr == LIBUSB_ERROR_NO_DEVICE ? X502_ERR_DEVICE_DISCONNECTED :
                     X502_ERR_DEVICE_OPEN;

    if (err == X502_ERR_OK) {
        t_lboot_devinfo lboot_info;

        err = f_ioreq(devhnd, E502_CM4_CMD_GET_MODULE_INFO, 0, NULL, 0, &lboot_info,
                              sizeof(lboot_info), NULL, 0);
        if (err == X502_ERR_OK) {
            if (strcmp(lboot_info.devname, devrec->devname)) {
                err = X502_ERR_INVALID_DEVICE;
            } else {
                e502_devinfo_init(&hnd->info, &lboot_info);
            }
        }
    }

    if (err == X502_ERR_OK) {
        t_usb_iface_data *usb_data = calloc(1, sizeof(t_usb_iface_data));
        unsigned stream;
        struct libusb_config_descriptor* cfg;

        usb_data->devhnd = devhnd;

        /* Чтобы определить номера используемых конечных точек, получаем
         * конфигурацию устройства и находим по одной конечной точки типа
         * bulk на каждое направление (in/out) */
        usberr = libusb_get_config_descriptor(dev, 0, &cfg);
        if (!usberr) {
            int intf_idx, s, fnd_in=0, fnd_out=0;
            for (intf_idx = 0; intf_idx < cfg->bNumInterfaces; intf_idx++) {
                for (s=0; s < cfg->interface[intf_idx].num_altsetting; s++) {
                    int e;
                    for (e=0; e < cfg->interface[intf_idx].altsetting[s].bNumEndpoints; e++) {
                        const struct libusb_endpoint_descriptor *ep_descr =
                                &cfg->interface[intf_idx].altsetting[s].endpoint[e];
                        if ((ep_descr->bmAttributes & 0x3)==
                                LIBUSB_TRANSFER_TYPE_BULK ) {
                            if ((ep_descr->bEndpointAddress & 0x80)
                                    == LIBUSB_ENDPOINT_IN) {
                                if (!fnd_in) {
                                     usb_data->streams[X502_STREAM_CH_IN].addr = ep_descr->bEndpointAddress;
                                     usb_data->streams[X502_STREAM_CH_IN].pkt_size = ep_descr->wMaxPacketSize;
                                     fnd_in = 1;
                                }
                            } else {
                                if (!fnd_out) {
                                    usb_data->streams[X502_STREAM_CH_OUT].addr = ep_descr->bEndpointAddress;
                                    usb_data->streams[X502_STREAM_CH_OUT].pkt_size = ep_descr->wMaxPacketSize;
                                    fnd_out = 1;
                                }
                            }
                        }
                    }
                }
            }
            libusb_free_config_descriptor(cfg);
            if (!fnd_in || !fnd_out)
                err = X502_ERR_INVALID_USB_CONFIGURATION;
        } else {
            err = X502_ERR_INVALID_USB_CONFIGURATION;
        }

        for (stream=0; stream < X502_STREAM_CH_CNT; stream++) {
            usb_data->streams[stream].mutex = osspec_mutex_create();
            usb_data->streams[stream].user_wake_evt = osspec_event_create(0);
            usb_data->streams[stream].usb_wake_evt = osspec_event_create(0);
            usb_data->streams[stream].dev = hnd;
            usb_data->streams[stream].thread = OSSPEC_INVALID_THREAD;

            if ((usb_data->streams[stream].mutex == OSSPEC_INVALID_MUTEX) ||
                    (usb_data->streams[stream].user_wake_evt == OSSPEC_INVALID_EVENT) ||
                    (usb_data->streams[stream].usb_wake_evt == OSSPEC_INVALID_EVENT))  {
                err = X502_ERR_MUTEX_CREATE;
            }
        }

        if (err == X502_ERR_OK) {
            hnd->iface_data = usb_data;
        } else {
            for (stream=0; stream < X502_STREAM_CH_CNT; stream++) {
                if (usb_data->streams[stream].mutex != OSSPEC_INVALID_MUTEX)
                    osspec_mutex_destroy(usb_data->streams[stream].mutex);
                if (usb_data->streams[stream].user_wake_evt != OSSPEC_INVALID_EVENT)
                    osspec_event_destroy(usb_data->streams[stream].user_wake_evt);
                if (usb_data->streams[stream].usb_wake_evt != OSSPEC_INVALID_EVENT)
                    osspec_event_destroy(usb_data->streams[stream].usb_wake_evt);
            }
            free(usb_data);
        }
    }

    if (err != X502_ERR_OK) {
        if (devhnd != NULL) {
            libusb_release_interface(devhnd, 0);
            libusb_close(devhnd);
        }
    }

    return err;
}

static int32_t f_iface_close(t_x502_hnd hnd) {
    t_usb_iface_data *usb_data = (t_usb_iface_data*)hnd->iface_data;
    int32_t err = 0;
    if (usb_data != NULL) {
        unsigned ch;

        for (ch=0; ch < sizeof(usb_data->streams)/sizeof(usb_data->streams[0]); ch++) {
            if (usb_data->streams[ch].mutex != OSSPEC_INVALID_MUTEX) {
                osspec_mutex_destroy(usb_data->streams[ch].mutex);
                usb_data->streams[ch].mutex = OSSPEC_INVALID_MUTEX;
            }
            if (usb_data->streams[ch].user_wake_evt != OSSPEC_INVALID_EVENT) {
                osspec_event_destroy(usb_data->streams[ch].user_wake_evt);
                usb_data->streams[ch].user_wake_evt = OSSPEC_INVALID_EVENT;
            }
            if (usb_data->streams[ch].usb_wake_evt != OSSPEC_INVALID_EVENT) {
                osspec_event_destroy(usb_data->streams[ch].usb_wake_evt);
                usb_data->streams[ch].usb_wake_evt = OSSPEC_INVALID_EVENT;
            }
        }

        libusb_release_interface(usb_data->devhnd, 0);
        libusb_close(usb_data->devhnd);


        free(hnd->iface_data);
        hnd->iface_data = NULL;
    }
    return err;
}


static int32_t f_iface_free_devinfo_ptr(t_x502_devrec_inptr *devinfo_ptr) {
    libusb_device *dev = (libusb_device*)devinfo_ptr->iface_data;
    libusb_unref_device(dev);
    free(devinfo_ptr);
    return X502_ERR_OK;
}



static void LIBUSB_CALL f_usb_transf_cb(struct libusb_transfer *transfer) {
    *((int*)transfer->user_data) = TRANSF_CPL_COMPLETED;
}


static OSSPEC_THREAD_FUNC_RET OSSPEC_THREAD_FUNC_CALL f_usb_rx_thread_func(void *arg) {
    t_usb_iface_data *usb_data = (t_usb_iface_data*)arg;
    struct libusb_transfer *transfers[USB_BULK_RX_MAX_TRANSF_CNT]; /* запросы usb */
    volatile int transf_completed[USB_BULK_RX_MAX_TRANSF_CNT];
    t_transf_info *info = &usb_data->streams[X502_STREAM_CH_IN];
    unsigned transf_pos=0;
    unsigned cpl_pos = 0;
    unsigned transf_check_pos=0;
    unsigned cpl_check_pos=0;

    unsigned idx;
    int err = 0;
    info->err = 0;

    for (idx = 0; idx < USB_BULK_RX_MAX_TRANSF_CNT; idx++) {
        transfers[idx] = libusb_alloc_transfer(0);
        transf_completed[idx] = TRANSF_CPL_IDLE;
        if (transfers[idx]==NULL) {
            err = X502_ERR_MEMORY_ALLOC;
        }
    }

    while (!err && !info->stop_req) {
        /* обрабатываем завершенные передачи в порядке добавления */
        while (transf_completed[transf_check_pos] == TRANSF_CPL_COMPLETED) {
            t_rx_cpl_part *cpl = &info->rx.cpls[cpl_check_pos];
            struct libusb_transfer *transfer = transfers[transf_check_pos];

            osspec_mutex_lock(info->mutex, MUTEX_STREAM_LOCK_TOUT);
            if ((transfer->status == LIBUSB_TRANSFER_COMPLETED)
                    || (transfer->status == LIBUSB_TRANSFER_TIMED_OUT)
                    || (transfer->status == LIBUSB_TRANSFER_CANCELLED)) {
                cpl->size = transfer->actual_length/sizeof(uint32_t);
                info->rx.buf_get_rdy += cpl->size;
                cpl->state = RX_STATE_RDY;
            } else {
                cpl->state = RX_STATE_ERR;
            }
            transf_completed[transf_check_pos] = TRANSF_CPL_IDLE;
            osspec_event_set(info->user_wake_evt);

            if (++transf_check_pos == USB_BULK_RX_MAX_TRANSF_CNT)
                transf_check_pos = 0;
            if (++cpl_check_pos == info->rx.cpl_cnt)
                cpl_check_pos = 0;

            osspec_mutex_release(info->mutex);
        }


        /* пробуем запустить новые трансферы на передачу */
        while ((transf_completed[transf_pos] == TRANSF_CPL_IDLE) &&
               (info->rx.cpls[cpl_pos].state == RX_STATE_IDLE) && !err) {

            info->rx.cpls[cpl_pos].addr = &info->data[info->rx.transf_size*cpl_pos];
            libusb_fill_bulk_transfer(transfers[transf_pos], usb_data->devhnd, info->addr,
                                     (unsigned char*)info->rx.cpls[cpl_pos].addr,
                                     info->rx.transf_size*sizeof(info->data[0]),
                                     f_usb_transf_cb, (void*)&transf_completed[transf_pos],
                                     USB_BULK_RX_TRANSFER_TOUT);
            info->rx.cpls[cpl_pos].state = RX_STATE_BUSY;
            transf_completed[transf_pos] = TRANSF_CPL_BUSY;

            if (libusb_submit_transfer(transfers[transf_pos])==0) {
                osspec_mutex_lock(info->mutex, MUTEX_STREAM_LOCK_TOUT);
                osspec_mutex_release(info->mutex);
                if (++transf_pos == USB_BULK_RX_MAX_TRANSF_CNT)
                    transf_pos = 0;
            } else {
                err = X502_ERR_RECV;
                transf_completed[transf_pos] = TRANSF_CPL_IDLE;
                info->rx.cpls[cpl_pos].state = RX_STATE_ERR;
            }

            if (++cpl_pos == info->rx.cpl_cnt)
                cpl_pos = 0;
        }

        if (!err) {
            int wt_buf_rdy = 0;


            osspec_mutex_lock(info->mutex, MUTEX_STREAM_LOCK_TOUT);
            if (info->rx.cpls[cpl_pos].state != RX_STATE_IDLE) {
                wt_buf_rdy = 1;
                osspec_event_clear(info->usb_wake_evt);
            }
            osspec_mutex_release(info->mutex);


            if (transf_completed[transf_check_pos] != TRANSF_CPL_IDLE) {
                int lusberr;
                /* иначе ждем событий от USB */
                struct timeval tv;
                tv.tv_sec =0;
                tv.tv_usec = 200 * 1000;
                lusberr = libusb_handle_events_timeout_completed(NULL, &tv, (int*)&transf_completed[transf_check_pos]);

                if ((lusberr != LIBUSB_SUCCESS) && (lusberr != LIBUSB_ERROR_INTERRUPTED)) {
                    err = X502_ERR_RECV;
                }

            } else if (wt_buf_rdy) {
                /* если буфер занят - ждем события от потока чтения */
                osspec_event_wait(info->usb_wake_evt, OSSPEC_TIMEOUT_INFINITY);
            }
        }
    }

    /* отменяем все поставленные запросы */
    for (idx = 0; idx < USB_BULK_RX_MAX_TRANSF_CNT; idx++) {
       unsigned pos = (transf_check_pos + idx) % USB_BULK_RX_MAX_TRANSF_CNT;
       if (transf_completed[pos] == TRANSF_CPL_BUSY) {
           if (libusb_cancel_transfer(transfers[pos]) !=LIBUSB_SUCCESS)
               transf_completed[pos] = TRANSF_CPL_COMPLETED;
       }
    }

    /* ждем завершения всех запросов */
    while (transf_completed[transf_check_pos] != TRANSF_CPL_IDLE) {
        if (transf_completed[transf_check_pos] == TRANSF_CPL_BUSY) {
            struct timeval ztv = {0,0};
            libusb_handle_events_timeout(NULL, &ztv);
        } else {
            transf_completed[transf_check_pos] = TRANSF_CPL_IDLE;
            if (++transf_check_pos == USB_BULK_RX_MAX_TRANSF_CNT)
                transf_check_pos = 0;
        }
    }

    /* очищаем ресурсы */
    for (idx = 0; idx < USB_BULK_RX_MAX_TRANSF_CNT; idx++) {
        if (transfers[idx] != NULL) {
            libusb_free_transfer(transfers[idx]);
        }
    }

    if (info->err == X502_ERR_OK)
       info->err = err;

    return 0;
}



static OSSPEC_THREAD_FUNC_RET OSSPEC_THREAD_FUNC_CALL f_usb_tx_thread_func(void *arg) {
    t_usb_iface_data *usb_data = (t_usb_iface_data*)arg;
    struct libusb_transfer *transfers[USB_BULK_TX_MAX_TRANSF_CNT]; /* запросы usb */
    t_transf_info *info = &usb_data->streams[X502_STREAM_CH_OUT];
    volatile int transf_completed[USB_BULK_RX_MAX_TRANSF_CNT];
    unsigned transf_pos=0;
    unsigned transf_check_pos=0;
    unsigned idx;
    int err = 0;
    int last_full_packet = 0; /* признак, что последний пакет был полный, чтобы
                                 послять нулевой пакет */
    unsigned snd_pos = 0;


    info->err = 0;
    info->tx.busy_size = 0;

    for (idx = 0; (idx < USB_BULK_TX_MAX_TRANSF_CNT) && (err == X502_ERR_OK); idx++) {
        transfers[idx] = libusb_alloc_transfer(0);
        transf_completed[idx] = TRANSF_CPL_IDLE;
        if (transfers[idx]==NULL) {
            err = X502_ERR_MEMORY_ALLOC;
        }
    }

    while (!err && !info->stop_req) {
        uint32_t snd_rdy_size;
        osspec_mutex_lock(info->mutex, MUTEX_STREAM_LOCK_TOUT);
        snd_rdy_size = info->buf_size - info->tx.buf_put_rdy - info->tx.busy_size;

        if (snd_rdy_size == 0)
            osspec_event_clear(info->usb_wake_evt);
        osspec_mutex_release(info->mutex);

        /* обрабатываем завершенные передачи */
        while (transf_completed[transf_check_pos] == TRANSF_CPL_COMPLETED) {
            struct libusb_transfer *transfer = transfers[transf_check_pos];
            if ((transfer->status != LIBUSB_TRANSFER_COMPLETED)
                    && (transfer->status != LIBUSB_TRANSFER_CANCELLED)) {
                err = X502_ERR_SEND;
            }


            osspec_mutex_lock(info->mutex, MUTEX_STREAM_LOCK_TOUT);
            info->tx.buf_put_rdy += transfer->length/sizeof(info->data[0]);
            info->tx.busy_size -= transfer->length/sizeof(info->data[0]);
            osspec_event_set(info->user_wake_evt);
            osspec_mutex_release(info->mutex);

            transf_completed[transf_check_pos] = TRANSF_CPL_IDLE;

            if (++transf_check_pos == USB_BULK_RX_MAX_TRANSF_CNT)
                transf_check_pos = 0;
        }


        while (((snd_rdy_size != 0) || (last_full_packet && (info->tx.busy_size==0)))
               && (transf_completed[transf_pos] == TRANSF_CPL_IDLE)
                && (err == X502_ERR_OK)) {
            uint32_t snd_size = snd_rdy_size;
            if (snd_size > USB_BULK_TX_MAX_TRANSF_SIZE)
                snd_size = USB_BULK_TX_MAX_TRANSF_SIZE;
            if (snd_size > (info->buf_size - snd_pos))
                snd_size = info->buf_size - snd_pos;

            libusb_fill_bulk_transfer(transfers[transf_pos], usb_data->devhnd, info->addr,
                                        (unsigned char*)&info->data[snd_pos],
                                        snd_size*sizeof(info->data[0]),
                                        f_usb_transf_cb,
                                        (void*)&transf_completed[transf_pos],
                                        USB_BULK_TX_TRANSFER_TOUT);



            transf_completed[transf_pos] = TRANSF_CPL_BUSY;
            if (libusb_submit_transfer(transfers[transf_pos])==0) {
                osspec_mutex_lock(info->mutex, MUTEX_STREAM_LOCK_TOUT);
                info->tx.busy_size+=snd_size;
                osspec_mutex_release(info->mutex);
                if (++transf_pos == USB_BULK_TX_MAX_TRANSF_CNT)
                    transf_pos = 0;
                snd_pos+=snd_size;
                if (snd_pos==info->buf_size)
                    snd_pos = 0;
                snd_rdy_size-=snd_size;

                last_full_packet = (snd_size != 0) && ((snd_size % (info->pkt_size/sizeof(info->data[0]))) == 0);
            } else {
                transf_completed[transf_pos] = TRANSF_CPL_IDLE;
                err = X502_ERR_SEND;
            }
        }


        if (err == X502_ERR_OK) {
            if (info->tx.busy_size!=0) {
                /* иначе ждем событий от USB */
                struct timeval tv;
                tv.tv_sec =0;
                tv.tv_usec = 200 * 1000;
                libusb_handle_events_timeout_completed(NULL, &tv,
                                                       (int*)&transf_completed[transf_check_pos]);
            } else if ((snd_rdy_size==0) && !last_full_packet) {
                /* если буфер занят - ждем события от потока чтения */
                osspec_event_wait(info->usb_wake_evt, OSSPEC_TIMEOUT_INFINITY);
            }
        }
    }

    /* отменяем все поставленные запросы */
    for (idx = 0; idx < USB_BULK_RX_MAX_TRANSF_CNT; idx++) {
       unsigned pos = (transf_check_pos + idx) % USB_BULK_RX_MAX_TRANSF_CNT;
       if (transf_completed[pos] == TRANSF_CPL_BUSY) {
           if (libusb_cancel_transfer(transfers[pos]) !=LIBUSB_SUCCESS)
               transf_completed[pos] = TRANSF_CPL_COMPLETED;
       }
    }

    /* ждем завершения всех запросов */
    while (transf_completed[transf_check_pos] != TRANSF_CPL_IDLE) {
        if (transf_completed[transf_check_pos] == TRANSF_CPL_BUSY) {
            struct timeval ztv = {0,0};
            libusb_handle_events_timeout(NULL, &ztv);
        } else {
            transf_completed[transf_check_pos] = TRANSF_CPL_IDLE;
            if (++transf_check_pos == USB_BULK_RX_MAX_TRANSF_CNT)
                transf_check_pos = 0;
        }
    }

    /* очищаем ресурсы */
    for (idx = 0; idx < USB_BULK_RX_MAX_TRANSF_CNT; idx++) {
        if (transfers[idx] != NULL) {
            libusb_free_transfer(transfers[idx]);
        }
    }

    if (info->err == X502_ERR_OK)
       info->err = err;


    return 0;
}


static void f_stream_init(uint32_t ch, t_transf_info *info) {
    info->stop_req = 0;
    info->err = 0;
    if (ch==X502_STREAM_CH_IN) {
        unsigned i;
        info->rx.cpl_get_pos = 0;
        info->rx.buf_get_rdy = 0;


        for (i=0; i < info->rx.cpl_cnt; i++) {
            info->rx.cpls[i].state = RX_STATE_IDLE;
        }
    }  else {
        info->tx.buf_pos_put = 0;
        info->tx.buf_put_rdy = info->buf_size;
    }
}

static int32_t f_iface_stream_cfg(t_x502_hnd hnd, uint32_t ch, t_x502_stream_ch_params *params) {
    t_usb_iface_data *usb_data = (t_usb_iface_data *)hnd->iface_data;
    t_transf_info *info = &usb_data->streams[ch];
    int err = 0;

    /** @todo вариант изменения размеров буфера в работе */
    if (info->data == NULL) {
        info->buf_size = params->buf_size;


        if (ch==X502_STREAM_CH_IN) {
            info->rx.transf_size = ((params->step+127)/128)*128;
            if (info->rx.transf_size > USB_BULK_RX_MAX_TRANSF_SIZE)
                info->rx.transf_size = USB_BULK_RX_MAX_TRANSF_SIZE;

            info->rx.cpl_cnt = info->buf_size/info->rx.transf_size;
            /* буфер разбиваем на столько частей, чтобы можно было запустить
             * в параллель все USB_BULK_RX_MAX_TRANSF_CNT трансферов */
            if (info->rx.cpl_cnt < USB_BULK_RX_MAX_TRANSF_CNT)
                info->rx.cpl_cnt = USB_BULK_RX_MAX_TRANSF_CNT;
            info->buf_size = info->rx.cpl_cnt*info->rx.transf_size;

            info->rx.cpls = malloc(info->rx.cpl_cnt * sizeof(info->rx.cpls[0]));
            if (info->rx.cpls == NULL) {
                err = X502_ERR_MEMORY_ALLOC;
            }
        }

        if (err == X502_ERR_OK) {
            info->data = malloc(info->buf_size*sizeof(info->data[0]));
            if (info->data == NULL) {
                err = X502_ERR_MEMORY_ALLOC;
            }
        }

        if (err == X502_ERR_OK) {
            f_stream_init(ch, info);
        }

        if (err == X502_ERR_OK) {
            uint16_t send_step = params->step > 0xFFFF ? 0xFFFF : params->step;
            err = f_ioreq(usb_data->devhnd, E502_CM4_CMD_STREAM_SET_STEP,
                          (ch<<16) | send_step, NULL, 0, NULL, 0, NULL, 0);
        }


        if (err != X502_ERR_OK) {
            f_iface_stream_free(hnd, ch, 0);
        }
    }


    return err;
}






static int32_t f_iface_stream_start(t_x502_hnd hnd, uint32_t ch, uint32_t flags) {
    t_usb_iface_data *usb_data = (t_usb_iface_data *)hnd->iface_data;
    int32_t err = X502_ERR_OK;

    if (err == X502_ERR_OK) {
        t_transf_info *info = &usb_data->streams[ch];
        f_stream_init(ch, info);
        info->thread = osspec_thread_create(ch == X502_STREAM_CH_IN ?
                                                f_usb_rx_thread_func : f_usb_tx_thread_func,
                                            usb_data, 0);
        if (info->thread == OSSPEC_INVALID_THREAD) {
            err = X502_ERR_THREAD_START;
        }
    }

    if (!err && !(flags & X502_STREAM_FLAG_NO_REQUEST)) {
        err = f_ioreq(usb_data->devhnd, E502_CM4_CMD_STREAM_START, (ch<<16),
                      NULL, 0, NULL, 0, NULL, 0);
    }

    return err;
}

static int32_t f_iface_stream_stop(t_x502_hnd hnd, uint32_t ch, uint32_t flags) {
    t_usb_iface_data *usb_data = (t_usb_iface_data *)hnd->iface_data;
    t_transf_info *info = &usb_data->streams[ch];
    int err = X502_ERR_OK;
    int ioctl_err = X502_ERR_OK;
    int32_t running;

    if (!(flags & X502_STREAM_FLAG_NO_REQUEST)) {
        ioctl_err = hnd->iface_hnd->stream_running(hnd, ch, &running);
        if (!ioctl_err && running) {
            ioctl_err = f_ioreq(usb_data->devhnd, E502_CM4_CMD_STREAM_STOP, (ch << 16),
                                NULL, 0, NULL, 0, NULL, 0);
        }
    }


    if (info->thread != OSSPEC_INVALID_THREAD) {
        info->stop_req = 1;
        osspec_event_set(info->usb_wake_evt);
        err = osspec_thread_wait(info->thread, STREAM_STOP_TOUT);
        info->thread  = OSSPEC_INVALID_THREAD;
    }

    return err != X502_ERR_OK ? err : ioctl_err;
}


static int32_t f_iface_stream_free(t_x502_hnd hnd, uint32_t ch, uint32_t flags) {
    int32_t err;
    t_usb_iface_data *usb_data = (t_usb_iface_data *)hnd->iface_data;
    t_transf_info *info = &usb_data->streams[ch];

    err = hnd->iface_hnd->stream_stop(hnd, ch, flags);

    free(info->data);
    info->data = NULL;

    if (ch==X502_STREAM_CH_IN) {
        free(info->rx.cpls);
        info->rx.cpls = NULL;
        info->rx.cpl_cnt = 0;
        info->rx.transf_size = 0;
    }

    return err;
}




static int32_t f_iface_stream_read(t_x502_hnd hnd, uint32_t *buf, uint32_t size, uint32_t tout) {
    int32_t recvd = 0;
    t_usb_iface_data *usb_data = (t_usb_iface_data *)hnd->iface_data;
    t_transf_info *info = &usb_data->streams[X502_STREAM_CH_IN];
    t_ltimer tmr;
    int32_t err = info->data == NULL ? X502_ERR_STREAM_IS_NOT_RUNNING : X502_ERR_OK;

    if (err == X502_ERR_OK) {
        ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(tout));

        do {
            int check_next = 1;
            err = info->err;

            while (!err && check_next) {
                t_rx_cpl_part *cpl = &info->rx.cpls[info->rx.cpl_get_pos];
                int cur_state = cpl->state;
                check_next = 0;

                if (cur_state == RX_STATE_ERR) {
                    err = X502_ERR_RECV;
                } else if (cur_state == RX_STATE_RDY) {
                    osspec_mutex_lock(info->mutex, MUTEX_STREAM_LOCK_TOUT);

                    if (cpl->size) {
                        uint32_t cpy_size = cpl->size;
                        if (cpy_size > size)
                            cpy_size = size;
                        if (cpy_size) {
                            memcpy(&buf[recvd], cpl->addr, cpy_size*sizeof(buf[0]));
                            recvd+=cpy_size;
                            size-=cpy_size;
                            cpl->size -= cpy_size;
                            cpl->addr += cpy_size;
                            info->rx.buf_get_rdy -= cpy_size;
                        }
                    }

                    if (cpl->size==0) {
                        cpl->state = RX_STATE_IDLE;
                        osspec_event_set(info->usb_wake_evt);
                        check_next = 1;
                    }
                    osspec_mutex_release(info->mutex);
                }

                if (check_next) {
                    if (++info->rx.cpl_get_pos==info->rx.cpl_cnt)
                        info->rx.cpl_get_pos = 0;
                }
            }



            if (!err && (size!=0)) {
                osspec_mutex_lock(info->mutex, MUTEX_STREAM_LOCK_TOUT);
                if ((info->rx.cpls[info->rx.cpl_get_pos].state == RX_STATE_IDLE)
                        || ((info->rx.cpls[info->rx.cpl_get_pos].state == RX_STATE_BUSY))) {
                    osspec_event_clear(info->user_wake_evt);
                }
                osspec_mutex_release(info->mutex);

                osspec_event_wait(info->user_wake_evt, LTIMER_CLOCK_TICKS_TO_MS(ltimer_expiration(&tmr)));
            }
        } while (!err && (size!=0) && !ltimer_expired(&tmr));
    }

    return err == X502_ERR_OK ? recvd : err;
}



static int32_t f_iface_stream_write(t_x502_hnd hnd, const uint32_t *buf, uint32_t size, uint32_t tout) {
    t_usb_iface_data *usb_data = (t_usb_iface_data *)hnd->iface_data;
    t_transf_info *info = &usb_data->streams[X502_STREAM_CH_OUT];
    t_ltimer tmr;
    int32_t sent = 0;
    int32_t err = info->data == NULL ? X502_ERR_STREAM_IS_NOT_RUNNING : X502_ERR_OK;

    if (err == X502_ERR_OK) {
        ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(tout));

        do {
            uint32_t rdy_size, snd_size;
            snd_size = rdy_size = info->tx.buf_put_rdy;
            err = info->err;

            if (!err) {
                if (rdy_size == 0) {
                    osspec_event_wait(info->user_wake_evt,
                                      LTIMER_CLOCK_TICKS_TO_MS(ltimer_expiration(&tmr)));
                } else {
                    uint32_t end_size;
                    uint32_t put_pos;
                    if (snd_size > size)
                        snd_size = size;
                    end_size = info->buf_size - info->tx.buf_pos_put;
                    if (snd_size >= end_size) {
                        memcpy(&info->data[info->tx.buf_pos_put], &buf[sent], end_size*sizeof(buf[0]));
                        if (snd_size!=end_size) {
                            memcpy(&info->data[0], &buf[sent+end_size], (snd_size-end_size)*sizeof(buf[0]));
                        }
                        put_pos = snd_size-end_size;
                    } else {
                        memcpy(&info->data[info->tx.buf_pos_put], &buf[sent], snd_size*sizeof(buf[0]));
                        put_pos = info->tx.buf_pos_put + snd_size;
                    }

                    osspec_mutex_lock(info->mutex, MUTEX_STREAM_LOCK_TOUT);
                    info->tx.buf_pos_put  = put_pos;
                    info->tx.buf_put_rdy -= snd_size;

                    if (info->tx.buf_put_rdy==0) {
                        osspec_event_clear(info->user_wake_evt);
                    }

                    osspec_event_set(info->usb_wake_evt);

                    osspec_mutex_release(info->mutex);


                    sent+=snd_size;
                    size-=snd_size;
                }
            }
        } while (!err && (size!=0) && !ltimer_expired(&tmr));
    }
    return err == X502_ERR_OK ? sent : err;
}



static int32_t f_iface_stream_get_rdy_cnt(t_x502_hnd hnd, uint32_t ch, uint32_t *rdy_cnt) {
    t_usb_iface_data *usb_data = (t_usb_iface_data *)hnd->iface_data;
    int32_t err=0;
    if (ch == X502_STREAM_CH_IN) {
        *rdy_cnt = usb_data->streams[X502_STREAM_CH_IN].rx.buf_get_rdy;
    } else if (ch == X502_STREAM_CH_OUT) {
        *rdy_cnt = usb_data->streams[X502_STREAM_CH_OUT].tx.buf_put_rdy;
    } else {
        err = X502_ERR_INVALID_STREAM_CH;

    }
    return err;
}






static int32_t f_ioreq(libusb_device_handle *handle, uint32_t cmd_code, uint32_t param,
                   const void* snd_data, uint32_t snd_size,
                   void* rcv_data, uint32_t recv_size, uint32_t* recvd_size, uint32_t tout) {
    int32_t err = 0;
    uint8_t req_type = LIBUSB_REQUEST_TYPE_VENDOR;
    uint16_t len;
    uint8_t* iobuf;
    int usbres = 0;
    if (tout == 0)
        tout = E502_USB_REQ_TOUT;

    if (recv_size > 0) {
        req_type |= LIBUSB_ENDPOINT_IN;
        len = recv_size;
        iobuf = rcv_data;
    } else {
        req_type |= LIBUSB_ENDPOINT_OUT;
        len = snd_size;
        iobuf = (uint8_t*)snd_data;
    }

    if (!err) {
        usbres = libusb_control_transfer(handle,
                req_type, cmd_code & 0xFF,
                param & 0xFFFF,
                (param >> 16) & 0xFFFF,
                iobuf, len, tout);
        if (usbres < 0) {
            err = (usbres == LIBUSB_ERROR_NO_DEVICE ? X502_ERR_DEVICE_DISCONNECTED :
                                                     X502_ERR_IOCTL_FAILD);
        } else {
            if (recvd_size!=NULL) {
                *recvd_size = usbres;
            } else if (usbres != len) {
                err = X502_ERR_IOCTL_INVALID_RESP_SIZE;
            }
        }

        //если управляющий запрос не выполнен успешно, то пытаемся получить
        //код ошибки из устройства
        if (err == X502_ERR_IOCTL_FAILD) {
            uint32_t devres;
            usbres = libusb_control_transfer(handle,
                    LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR,
                    E502_CM4_CMD_GET_LAST_ERROR,
                    0, 0,
                    (uint8_t*)&devres, sizeof(devres),
                    E502_USB_REQ_TOUT);

            //если успешно получили код ошибки устройства - то возвращаем его в качестве результата
            if ((usbres == sizeof(devres)) && (devres !=0)) {
                err = devres;
            }
        }
    }
    return err;
}


static int32_t f_iface_gen_ioctl(t_x502_hnd hnd, uint32_t cmd_code, uint32_t param,
                                 const void* snd_data, uint32_t snd_size,
                                 void* rcv_data, uint32_t recv_size, uint32_t* recvd_size,
                                 uint32_t tout) {
    t_usb_iface_data *usb_data = (t_usb_iface_data *)hnd->iface_data;
    return f_ioreq(usb_data->devhnd, cmd_code, param, snd_data, snd_size,
                   rcv_data, recv_size, recvd_size, tout);
}

static int32_t f_fill_devlist(libusb_device_handle *hnd, t_x502_devrec *info) {
    int32_t err = X502_ERR_OK;
    t_x502_devrec_inptr *devinfo_ptr = calloc(1, sizeof(t_x502_devrec_inptr));
    if (devinfo_ptr==NULL)  {
        err = X502_ERR_MEMORY_ALLOC;
    }

    if (err == X502_ERR_OK) {
        t_lboot_devinfo lboot_info;


        //получаем информацию о устройстве
        err = f_ioreq(hnd, E502_CM4_CMD_GET_MODULE_INFO, 0, NULL, 0, &lboot_info,
                      sizeof(lboot_info), NULL, 0);

        if (err == X502_ERR_OK) {
            uint32_t devflags;


            strcpy(info->devname, lboot_info.devname);
            strcpy(info->serial, lboot_info.serial);

            devinfo_ptr->iface = &f_usb_iface;
            devinfo_ptr->iface_data = libusb_ref_device(libusb_get_device(hnd));
            info->internal = devinfo_ptr;
            info->iface = X502_IFACE_USB;
            info->flags = X502_DEVFLAGS_IFACE_SUPPORT_USB;

            if (f_ioreq(hnd, E502_CM4_CMD_GET_DEVFLAGS, 0,
                        NULL, 0, &devflags, sizeof(devflags), NULL, 0) == X502_ERR_OK) {
                info->flags &= ~E502_CM4_DEVFLAGS;
                info->flags |= devflags;
            }
        }
    }

    if (err != X502_ERR_OK) {
        free(devinfo_ptr);
    }
    return err;
}



X502_EXPORT(int32_t) E502_UsbGetDevRecordsList2(t_x502_devrec *list, uint32_t size,
                                             uint32_t flags, uint32_t *devcnt, uint16_t idVendor, uint16_t idProduct) {
    uint32_t curcnt=0;
    int32_t err = X502_ERR_OK;
    libusb_device **usb_devlist;
    ssize_t usb_devlist_size, i;

    if (!f_lusb_init_done) {
        libusb_init(NULL);
#ifdef LIBUSB_DEBUG
        libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_DEBUG);
#endif
        f_lusb_init_done = 1;
    }


    //получаем список устройств
    usb_devlist_size = libusb_get_device_list(NULL, &usb_devlist);
    for (i=0; i < usb_devlist_size; i++) {
        //получаем дескриптор устройства для проверки vid, pid
        struct libusb_device_descriptor devdescr;
        libusb_device_handle* usbhnd = NULL;

        if (libusb_get_device_descriptor (usb_devlist[i], &devdescr) == 0) {
            //сравниваем VID, PID с индентификаторами загрузочного устройства
            if ((devdescr.idVendor == idVendor)
                    && (devdescr.idProduct == idProduct)) {
                int32_t cur_err;
                cur_err = libusb_open(usb_devlist[i], &usbhnd);
                if (cur_err == X502_ERR_OK) {
                    t_x502_devrec info;
                    int info_used = 0;
                    X502_DevRecordInit(&info);


                   if (f_fill_devlist(usbhnd, &info) == X502_ERR_OK) {
                       /* если нужны только не открытые, то уже открытое
                        * устройство пропускаем */
                       if (!(flags & X502_GETDEVS_FLAGS_ONLY_NOT_OPENED) ||
                               !(info.flags & X502_DEVFLAGS_DEVREC_OPENED)) {
                           /* если есть место в списке - то сохраняем
                            * полученную информацию */
                           if ((list!=NULL) && (curcnt < size)) {
                               list[curcnt] = info;
                               info_used = 1;
                           }
                           curcnt++;
                       }
                   }

                   if (!info_used)
                       X502_FreeDevRecordList(&info,1);

                   libusb_close(usbhnd);
                }
            }
        }
    }
    libusb_free_device_list(usb_devlist, 1);


    if (devcnt != NULL)
        *devcnt = curcnt;

    return err != X502_ERR_OK ? err  : curcnt > (uint32_t)size ? (int32_t)size : (int32_t)curcnt ;
}

X502_EXPORT(int32_t) E440_UsbGetDevRecordsList(t_x502_devrec* list, uint32_t size,
                                             uint32_t flags, uint32_t* devcnt) {
    return E502_UsbGetDevRecordsList2(list, size, flags, devcnt, E440_USB_VID, E440_USB_PID);
}

X502_EXPORT(int32_t) E502_UsbGetDevRecordsList(t_x502_devrec *list, uint32_t size,
                                             uint32_t flags, uint32_t *devcnt) {
    return E502_UsbGetDevRecordsList2(list, size, flags, devcnt, E502_USB_VID, E502_USB_PID);
}

X502_EXPORT(int32_t) E502_OpenUsb(t_x502_hnd hnd, const char* serial) {
    return X502_Open(hnd, serial, E502_DEVICE_NAME, E502_UsbGetDevRecordsList);
}

X502_EXPORT(int32_t) E502_UsbGetSerialList(char serials[][X502_SERIAL_SIZE], uint32_t size,
                           uint32_t flags, uint32_t *devcnt) {
   return X502_GetSerialList(serials, size, flags, devcnt, E502_DEVICE_NAME, E502_UsbGetDevRecordsList);
}


X502_EXPORT(int32_t) E16_UsbGetDevRecordsList(t_x502_devrec *list, uint32_t size,
                                             uint32_t flags, uint32_t *devcnt) {
    return E502_UsbGetDevRecordsList2(list, size, flags, devcnt, E16_USB_VID, E16_USB_PID);
}

X502_EXPORT(int32_t) E16_OpenUsb(t_x502_hnd hnd, const char* serial) {
    return X502_Open(hnd, serial, E16_DEVICE_NAME, E16_UsbGetDevRecordsList);
}

X502_EXPORT(int32_t) E16_UsbGetSerialList(char serials[][X502_SERIAL_SIZE], uint32_t size,
                           uint32_t flags, uint32_t *devcnt) {
   return X502_GetSerialList(serials, size, flags, devcnt, E16_DEVICE_NAME, E16_UsbGetDevRecordsList);
}
#else
#include "e502api.h"
X502_EXPORT(int32_t) E502_OpenUsb(t_x502_hnd hnd, const char* serial) {
    return X502_ERR_NOT_IMPLEMENTED;
}

X502_EXPORT(int32_t) E502_UsbGetSerialList(char serials[][X502_SERIAL_SIZE], uint32_t size,
                           uint32_t flags, uint32_t *devcnt) {
   return X502_ERR_NOT_IMPLEMENTED;
}
#endif






