/* Данный файл содержит общую логику работы с крейтом по интерфейсу USB,
 *  не зависящий от ОС и используемой библиотеки для доступа к устройству */
#include "ltrsrv_usb_p.h"
#include "ltrsrv_defs.h"
#include "ltrsrv_cfg.h"
#include "ltrsrv_log.h"
#include "ltrsrv_wait.h"

#include <stdlib.h>


t_usb_data* f_usb_crates[LTRSRV_MAX_CRATES_CNT];
int f_usb_crates_cnt = 0;

const t_usb_dev_id crate_usb_dev_ids[] = {
    { LTR_CRATE_TYPE_LTR010,    0x0471, 0x1010 }, // LTR010
    { LTR_CRATE_TYPE_LTR021,    0x0471, 0x2121 }, // LTR021
    { LTR_CRATE_TYPE_LTR030,    0x0471, 0x3030 }, // LTR030
    { LTR_CRATE_TYPE_LTR031,    0x0471, 0x3131 },  // LTR031
    { LTR_CRATE_TYPE_LTR_CEU_1, 0x2a52, 0x2122}, //LTR021M
    { LTR_CRATE_TYPE_UNKNOWN, 0, 0}
};


static t_ltrsrv_str_tbl f_usbinit_str[] = {
    {USB_INIT_GET_NAME, "Get device name"},
    {USB_INIT_GET_DESCR, "Get device description"},
    {USB_INIT_GET_BOOTLOADER_VER, "Get bootloader version"},
    {USB_INIT_GET_FIRM_VER, "Get firmware version"},
    {USB_INIT_GET_HARD_REV, "Get board revision"},
    {USB_INIT_GET_PRIMARY_IFACE, "Get primary interface"},
    {USB_INIT_DMAC_INIT_0, "DMAC init 0"},
    {USB_INIT_FPGA_RESET,  "FPGA reset"},
    {USB_INIT_FPGA_INIT,  "FPAG init"},
    {USB_INIT_DMAC_INIT_1, "DMAC init 1"},
    {USB_INIT_FPGA_READ_INFO, "Read FPGA info"},
    {USB_INIT_SWITCH_BOOTLOADER, "Switch to bootloader"},
    {USB_INIT_SWITCH_APPLICATION, "Switch to application"},
    {USB_INIT_FPGA_LOAD, "Load FPGA"},
    {USB_INIT_FPGA_LOAD_DONE, "Load FPGA done"},
    {USB_INIT_DONE, "Initialization done"},
    {USB_INIT_CLEANUP, "Cleanup"}
};

#define LOGSTR_USB_INIT_STAGE(cmd) ltrsrv_log_get_str(cmd, f_usbinit_str, sizeof(f_usbinit_str)/sizeof(f_usbinit_str[0]))


static void f_usb_free_data(t_usb_data* intf, t_usb_close_reasons reason);
static int f_usb_start_open_dev(t_usb_data *intf);


/* Анализ специфичных параметров крейта по имени крейта, включая особенности
   инициализации по USB */
static int f_usb_crate_init(t_ltr_crate* crate) {
    int err = ltrsrv_crate_init_spec(crate);
    if (!err) {
        t_usb_data* usb_data = (t_usb_data*)crate->intf_data;

        if ((crate->type == LTR_CRATE_TYPE_LTR021) ||
                (crate->type == LTR_CRATE_TYPE_LTR010)) {
            usb_data->fpga_info_req_size = 250;
        } else {
            usb_data->fpga_info_req_size = 255;
        }

        if (crate->type == LTR_CRATE_TYPE_LTR021)
            usb_data->flags |= USB_INTF_FLAGS_SPECIAL_FPGA_DATA_PROC;

        if (crate->type==LTR_CRATE_TYPE_LTR010) {
            usb_data->flags |= USB_INTF_FLAGS_LOAD_FPGA | USB_INIT_FLAGS_SWITCH_BOOTLOADER;
            usb_data->firm_file_name = LTRD_LTR010_FIRMWARE_FILE;
        }
    }
    return err;
}

//------------------------------------------------------------------------------
// функция инициализации dmac
//------------------------------------------------------------------------------
// RDMA_PARAM
// 0 - disable rdma
// 1 - start rdma if 0..1K
// 2 - start rdma if 1K..4K
// 3 - start rdma if 0..16K
// 4 - start rdma if 16K..
// 5 - start rdma if 16K..
// 6 - start rdma if 1K..
// 7 - start rdma if 0..
// WDMA_PARAM
// 0 - buffer length
// 1 - (     256)
// 2 - (  1*1024)
// 3 - (  4*1024)
// 4 - ( 16*1024)
// 5 - ( 64*1024)
// 6 - (128*1024)
// 7 - (256*1024)
//------------------------------------------------------------------------------
static int f_usb_init_dmac(t_usb_data* intf, uint8_t rdma_param, uint8_t wdma_param) {
    return intf->port_intf->start_ioctl(
                intf, LTR_USB_CMD_SET_DMA_PARAMETRS,
                (3UL << 16) | (((uint32_t)rdma_param) << 8) | wdma_param,
                0, 0, NULL);
}


/* обработка .ttf файла с прошивкой LTR010 и запись его блоками по 4096 байт.
 * если это был последний блок, то состояние изменяется на _INIT_FPGA_LOAD_DONE */
static int f_process_load_fpga(t_usb_data* intf) {
    uint8_t data[4096];
    int err=0;
    size_t rd_size =0;

    while (!err && !feof(intf->firm_file) && (rd_size < sizeof(data))) {
        int d;
        if (fscanf(intf->firm_file, "%3d,", &d) < 0) {
            err = LTRSRV_ERR_CRATE_FIRM_FILE_READ;
        } else {
            data[rd_size++] = d;
        }
    }

    if (!err && rd_size) {
        err = intf->port_intf->start_ioctl(
                    intf, LTR_USB_CMD_PUT_ARRAY,
                    LTR_CRATE_ADDR_FPGA_FIRMWARE+intf->fpga_load_size, 0,
                    (uint16_t)rd_size, data);
        if (!err)
            intf->fpga_load_size += rd_size;
    }


    if (!err && !rd_size) {
        intf->init_state = USB_INIT_FPGA_LOAD_DONE;
        fclose(intf->firm_file);
        intf->firm_file = NULL;
    }

    return err;
}



/* Проверка, все ли трансферы остановлены, и если это так - то
 * полная очистка памяти и закрытие хендла устройства.
 * Для неостановленных трансферов вызывается отмена трансфера, и
 * данная функция должна вызываеться из callback'а на завершение
 * трансфера, чтобы полная очистка произошла только когда все трансферы
 * выполнятся или будут отменены */
static void f_check_cleanup(t_usb_data* intf) {
    unsigned not_free_transfs = 0, i;

    if (intf->ctl.state!=USB_TRANSF_STATE_FREE) {
        not_free_transfs++;
        if (intf->ctl.state==USB_TRANSF_STATE_PROGRESS) {
            intf->port_intf->cancel_transfer(intf, &intf->ctl);
            intf->ctl.state=USB_TRANSF_STATE_CLEANUP;
        }
    }


    for (i=0; i < LTRSRV_USB_BULK_MAX_TRANSF_CNT; i++) {
        int pos = (i + intf->bulk_rd.get_pos) % LTRSRV_USB_BULK_MAX_TRANSF_CNT;
        if (intf->bulk_rd.transfs[pos].state!=USB_TRANSF_STATE_FREE) {
            not_free_transfs++;
            if (intf->bulk_rd.transfs[pos].state==USB_TRANSF_STATE_PROGRESS) {
                intf->port_intf->cancel_transfer(intf, &intf->bulk_rd.transfs[pos]);
                intf->bulk_rd.transfs[pos].state=USB_TRANSF_STATE_CLEANUP;
            }
        }
    }
    for (i=0; i < LTRSRV_USB_BULK_MAX_TRANSF_CNT; i++) {
        int pos = (i + intf->bulk_wr.get_pos) % LTRSRV_USB_BULK_MAX_TRANSF_CNT;
        if (intf->bulk_wr.transfs[pos].state!=USB_TRANSF_STATE_FREE) {
            not_free_transfs++;
            if (intf->bulk_wr.transfs[pos].state==USB_TRANSF_STATE_PROGRESS) {
                intf->port_intf->cancel_transfer(intf, &intf->bulk_wr.transfs[pos]);
                intf->bulk_wr.transfs[pos].state=USB_TRANSF_STATE_CLEANUP;
            }
        }
    }

    /* если все передачи завершены, то можем закрывать описатель */
    if (not_free_transfs == 0) {
        intf->port_intf->cleanup(intf);
    }
}

static void f_usb_unregister_crate(t_usb_data *intf) {
    if (intf->crate->par.state == LTR_CRATE_CONSTATE_WORK) {
        ltrsrv_wait_remove_tmr(&intf->snd_rdy_cb);
        ltrsrv_crate_unregister(intf->crate);
    }
}

static void f_set_dev_err(t_usb_data *intf, int err, int syserr) {
    intf->cur_err = err;
    intf->cur_syserr = syserr;
    /* если уже были ошибки, но более LTRSRV_USB_ERR_CLR_TOUT крейт работал
       нормально, то сбрасываем кол-во ошибок, чтобы не отключить совсем крейт
       из-за одиночных сбоев */
    if ((intf->err_reopen_cnt != 0) &&
        ((lclock_get_ticks() - intf->err_time) >
                LTIMER_MS_TO_CLOCK_TICKS(LTRSRV_USB_ERR_CLR_TOUT))) {
        intf->err_reopen_cnt = 0;
    }

    /* сохраняем время последней ошибки */
    intf->err_time = lclock_get_ticks();
    intf->err_reopen_cnt++;
}

static void f_check_disc_cb(t_socket sock, int event, void* data) {
    if (event==LTRSRV_EVENT_TOUT) {
        t_usb_data *intf = (t_usb_data *)data;

        if (intf->close_reason != USB_CLOSE_REASON_CRATE_REQ) {
            /* выводим сообщения об ошибках на события, о которых не выводим
               сообщения в момент ошибки (для проверки не корректное ли это отключение) */
            switch (intf->close_reason) {
                case USB_CLOSE_REASON_BULK_RECV_FAILD:
                    ltrsrv_log_syserr(intf->crate->log_str, intf->cur_err,
                                      intf->cur_syserr, "Crate data receive process error");
                    break;
                case USB_CLOSE_REASON_BULK_SEND_FAILD:
                    ltrsrv_log_syserr(intf->crate->log_str, intf->cur_err,
                                      intf->cur_syserr, "Crate data send process error");
                    break;
                case USB_CLOSE_REASON_BULK_OUT_OF_ORDER:
                    ltrsrv_log_syserr(intf->crate->log_str, intf->cur_err,
                                      intf->cur_syserr, "Crate transfer out of order complete");
                    break;
                default:
                    break;

            }

            f_usb_unregister_crate(intf);
            if (intf->err_reopen_cnt < LTRSRV_USB_RECONNECT_ERR_TRESHOLD) {
                f_usb_start_open_dev(intf);
            } else {
                ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, intf->crate->log_str, 0,
                               "Crate open retry limit exceeded. No more attempt until crate reconnect");
            }
        }
    }
}


void p_usb_cleanup_finish(t_usb_data *intf) {
    int i;

    if (intf->init_state != USB_INIT_CLEANUP_DONE) {
        intf->init_state = USB_INIT_CLEANUP_DONE;

        free(intf->ctl.buf);
        intf->ctl.buf = NULL;
        intf->ctl.len = 0;
        for (i=0; i < LTRSRV_USB_BULK_MAX_TRANSF_CNT; i++) {
            free(intf->bulk_rd.transfs[i].buf);
            intf->bulk_rd.transfs[i].buf = NULL;
            intf->bulk_rd.transfs[i].len = 0;

            free(intf->bulk_wr.transfs[i].buf);
            intf->bulk_wr.transfs[i].buf = NULL;
            intf->bulk_wr.transfs[i].len = 0;
        }

        free(intf->fpga_info);
        intf->fpga_info = NULL;

        free(intf->privdata);
        intf->privdata = NULL;
    }



    if (intf->close_reason == USB_CLOSE_REASON_DISCONNECT) {
        int fnd;

        ltrsrv_wait_remove_tmr(&intf->err_reopen_cnt);

        ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, intf->crate->log_str, 0, "usb crate info removed!");

        free(intf->crate->log_str);
        free(intf->crate);
        intf->crate = NULL;


        if (intf->port_intf->devid_free) {
            intf->port_intf->devid_free(intf->devid);
        }

        /* удаляем из списка используемый крейтов по usb, если эта информация тут есть */
        for (i=0, fnd=0; i < f_usb_crates_cnt; i++) {
            if (fnd) {
                f_usb_crates[i-1] = f_usb_crates[i];
            } else if (f_usb_crates[i]==intf) {
                fnd = 1;
            }
        }

        if (fnd)
            f_usb_crates_cnt--;

        free(intf);        
    } else {
        ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, intf->crate->log_str, 0, "usb crate preliminary cleanup done");
        ltrsrv_wait_add_tmr(&intf->err_reopen_cnt, LTRSRV_WAITFLG_AUTODEL,
                            intf->close_reason == USB_CLOSE_REASON_RESET ? 0 : LTRSRV_USB_DISK_CHECK_TOUT,
                            f_check_disc_cb, intf);
    }


}

static int f_start_all_read(t_usb_data* intf) {
   int err = 0;
   while (!err && (intf->bulk_rd.transfs[intf->bulk_rd.put_pos].state==USB_TRANSF_STATE_FREE))
       err = intf->port_intf->start_bulk(intf, &intf->bulk_rd, LTRSRV_USB_READ_READ_TOUT);
   return err;
}


static void f_intf_close(t_ltr_crate* hnd, int close_by_err) {
    int err = USB_CHECK_HND(hnd);
    if (!err) {
        t_usb_data *intf_data = (t_usb_data*)hnd->intf_data;
        if (!intf_data->close_reason) {
            intf_data->cur_err = close_by_err;
            f_usb_free_data(intf_data, USB_CLOSE_REASON_CRATE_REQ);
        }
    }
}

static int f_intf_reopen(t_ltr_crate* hnd) {
    int err = USB_CHECK_HND(hnd);
    if (!err) {
        t_usb_data *intf_data = (t_usb_data*)hnd->intf_data;
        ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, hnd->log_str, 0, "crate reopen request");
        p_usb_close_dev_by_err(intf_data, USB_CLOSE_REASON_RESET, 0, 0);
    }
    return err;
}

static int f_check_valid_for_ioctl(t_usb_data *intf) {
    int err = 0;
    /* проверка, что нет запроса на закрытие */
    if (intf->close_reason) {
        err = LTRSRV_ERR_CRATE_CLOSING;
        /* проверяем, что последовательность инициализации уже завершена */
    } else if (intf->init_state != USB_INIT_DONE) {
        err = LTRSRV_ERR_CRATE_CRATE_NOT_INIT;
        /*  проверяем, что сейчас команда ни одна не выполняется */
    }  else if (intf->ctl.state != USB_TRANSF_STATE_FREE) {
        err = LTRSRV_ERR_CRATE_CMD_IN_PROGRESS;
    }
    return err;
}


static int f_intf_regs_read(t_ltr_crate* crate, uint32_t addr, int size,
                            uint8_t* buf, t_ltr_crate_cmd_cb cb, void *data) {
    t_usb_data *intf;
    int err = USB_CHECK_HND(crate);

    if (!err) {
        intf = (t_usb_data *)crate->intf_data;
        err = f_check_valid_for_ioctl(intf);
    }

    if (!err) {
        err = intf->port_intf->start_ioctl(intf, LTR_USB_CMD_GET_ARRAY, addr,
                                           size, 0, NULL);
        if (!err) {
            intf->proc_cmd_cb = cb;
            intf->proc_data = data;
            intf->proc_buf = buf;
            intf->proc_len = size;
            intf->proc_rcv_size = NULL;
        }
    }
    return err;
}

static int f_intf_regs_write(t_ltr_crate* crate, uint32_t addr, int size,
                            const uint8_t* buf, t_ltr_crate_cmd_cb cb, void* data) {
    t_usb_data *intf;
    int err = USB_CHECK_HND(crate);
    if (!err) {
        intf = (t_usb_data *)crate->intf_data;
        err = f_check_valid_for_ioctl(intf);
    }

    if (!err) {
        err = intf->port_intf->start_ioctl(intf, LTR_USB_CMD_PUT_ARRAY, addr,
                                           0, size, buf);
        if (!err) {
            intf->proc_cmd_cb = cb;
            intf->proc_data = data;
            intf->proc_buf = NULL;
            intf->proc_len = size;
            intf->proc_rcv_size = NULL;
        }
    }
    return err;
}

static int f_intf_ioctl(t_ltr_crate* crate, uint32_t req, uint32_t param,
                        const uint8_t* snd_buf, uint32_t snd_size,
                        uint8_t* rcv_buf, uint32_t* rcv_size,
                        t_ltr_crate_cmd_cb cb, void* data) {
    t_usb_data *intf;
    int err = USB_CHECK_HND(crate);
    if (!err) {
        intf = (t_usb_data *)crate->intf_data;
        err = f_check_valid_for_ioctl(intf);
    }

    if (!err) {
        err = intf->port_intf->start_ioctl(intf, (uint8_t)req, param,
                                           rcv_size==NULL ? 0 : (uint16_t)*rcv_size,
                                           (uint16_t)snd_size, snd_buf);
        if (!err) {
            intf->proc_cmd_cb = cb;
            intf->proc_data = data;
            intf->proc_buf = rcv_buf;
            if ((rcv_size==NULL) || (*rcv_size==0)) {
                intf->proc_len = (uint16_t)snd_size;
                intf->proc_rcv_size = 0;
            } else {
                intf->proc_len = (uint16_t)*rcv_size;
                intf->proc_rcv_size = rcv_size;
            }
        }
    }
    return err;
}

static int f_intf_send(struct st_ltr_crate* crate, const uint8_t* buf, int size) {
    int err = USB_CHECK_HND(crate);
    int sent=0;
    /* при передаче разбиваем трансфер на блоки по LTRSRV_USB_BULK_TRANSF_SIZE
     * и копируем данные в спец. буфер для каждого трансфера. Если не достаточно
     * свободных трансферов, то вернем размер меньше нужного */
    if (!err) {
        t_usb_data *intf = (t_usb_data *)crate->intf_data;
        if (!intf->close_reason) {
            t_transf_params *bulk_trans = &intf->bulk_wr.transfs[intf->bulk_wr.put_pos];

            while (!err && (sent < size) && (bulk_trans->state==USB_TRANSF_STATE_FREE)) {
                if (bulk_trans->buf==NULL) {
                    bulk_trans->buf = (uint8_t*)malloc(LTRSRV_USB_BULK_TRANSF_SIZE);
                    if (bulk_trans->buf==NULL)
                        err = LTRSRV_ERR_MEMALLOC;
                }

                if (!err) {
                    bulk_trans->len = LTRSRV_USB_BULK_TRANSF_SIZE;
                    if (bulk_trans->len > (size-sent))
                        bulk_trans->len = (size-sent);
                    memcpy(bulk_trans->buf, buf, bulk_trans->len);

                    err = intf->port_intf->start_bulk(intf, &intf->bulk_wr,
                                                      LTRSRV_USB_BULK_SEND_TOUT);

                    //ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_LOW, crate->log_str, 0, "bulk wr start: %d words, result %d", bulk_trans->len, err);
                    if (!err) {
                        buf += bulk_trans->len;
                        sent += bulk_trans->len;
                    }
                }

                bulk_trans = &intf->bulk_wr.transfs[intf->bulk_wr.put_pos];
            }
        }
    }
    return err ? err : sent;
}


static void f_snd_rdy_cb(t_socket sock, int event, void* data) {
    if (event==LTRSRV_EVENT_TOUT) {
        t_usb_data *intf = (t_usb_data *)data;
        if (intf->snd_rdy_cb && !intf->close_reason) {
            t_ltr_crate_snd_rdy_cb cb = intf->snd_rdy_cb;
            intf->snd_rdy_cb = NULL;
            cb(intf->crate);
        }
    }
}

static int f_intf_wait_send_rdy(struct st_ltr_crate* crate, t_ltr_crate_snd_rdy_cb cb) {
    int err = USB_CHECK_HND(crate);
    if (!err) {
        t_usb_data *intf = (t_usb_data *)crate->intf_data;
        intf->snd_rdy_cb = cb;

        if (!intf->close_reason) {
            /* если уже готовы передавать данные (есть дескриптор) - то просто ставим
             * в очередь функцию для выполнения callback-а */
            if (intf->bulk_wr.transfs[intf->bulk_wr.put_pos].state == USB_TRANSF_STATE_FREE) {
                ltrsrv_wait_add_tmr(&intf->snd_rdy_cb, LTRSRV_WAITFLG_AUTODEL, 1, f_snd_rdy_cb, intf);
            }
        }
    }
    return err;
}

static void f_dev_disconnect(int d) {
    ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, f_usb_crates[d]->crate ?
                   f_usb_crates[d]->crate->log_str : LTRSRV_LOGSRC_CRATE_INIT,
                   0, "Crate disconnected!");
    f_usb_free_data(f_usb_crates[d], USB_CLOSE_REASON_DISCONNECT);
}

static int f_dev_get_idx(const void *devid, const t_usb_port_intf *port_intf) {
    int d;
    int fnd_idx = -1;

    for (d=f_usb_crates_cnt-1; (d>=0) && (fnd_idx < 0); d--) {
        if (port_intf->devid_cmp(devid, f_usb_crates[d]->devid)) {
            fnd_idx = d;
        }
    }
    return fnd_idx;
}


void p_usb_check_devlist_init(void) {
    int d;
    for (d=0; d < f_usb_crates_cnt; d++)
        f_usb_crates[d]->fnd = 0;
}


int p_usb_dev_is_new(const void *devid, const t_usb_port_intf *port_intf) {
    int idx = f_dev_get_idx(devid, port_intf);
    if (idx >= 0) {
        f_usb_crates[idx]->fnd = 1;
    }
    return (idx < 0);
}


static int f_usb_start_open_dev(t_usb_data *intf) {
    int err = LTRSRV_ERR_SUCCESS;
    /* заполняем поля, относящиеся к текущему интерфейсу */
    intf->crate->par.state = LTR_CRATE_CONSTATE_INITIALIZATION;
    intf->crate->par.intf = LTR_CRATE_IFACE_USB;
    intf->crate->par.mode = LTR_CRATE_MODE_WORK;
    intf->crate->close_req = 0;

    intf->close_reason = USB_CLOSE_REASON_NONE;
    intf->cur_err = LTRSRV_ERR_SUCCESS;
    intf->drop_init_wrds = 0;
    intf->drop_exception = 0;
    intf->flags = 0;

    intf->bulk_rd.put_pos = intf->bulk_rd.get_pos = 0;
    intf->bulk_wr.get_pos = intf->bulk_wr.put_pos = 0;
    intf->fpga_cur_info_size = 0;

    err = intf->port_intf->devopen(intf);
    ltrsrv_log_make_crate_srcstr(intf->crate);
    if (!err) {
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, intf->crate->log_str, 0, "Start initialization");
        intf->init_state = USB_INIT_GET_NAME;
        err = intf->port_intf->start_ioctl(intf, LTR_USB_CMD_GET_MODULE_NAME,
                                           0, LTR_CRATE_REG_NAME_SIZE, 0, NULL);
    } else {
        f_set_dev_err(intf, err, LTRSRV_LAST_SYSERR);
        ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, intf->crate->log_str, err, "Cannot open USB device");
        /* планируем повторную попытку через заданное время */
        ltrsrv_wait_add_tmr(&intf->err_reopen_cnt, LTRSRV_WAITFLG_AUTODEL,
                            LTRSRV_USB_DISK_CHECK_TOUT, f_check_disc_cb, intf);

    }
    return err;
}


int p_usb_init_new_dev(void *devid, const t_usb_port_intf *port_intf) {
    int err = 0;
    if (f_usb_crates_cnt == LTRSRV_MAX_CRATES_CNT) {
        err = LTRSRV_ERR_NO_FREE_CRATE_SLOT;
        port_intf->devid_free(devid);
    } else {
        t_ltr_crate *crate = ( t_ltr_crate*)calloc(1, sizeof(t_ltr_crate));
        t_usb_data *intf = (t_usb_data *)calloc(1, sizeof(t_usb_data));

        if ((crate!=NULL) && (intf!=NULL)) {


            intf->fnd = 1;
            intf->crate = crate;
            intf->port_intf = port_intf;
            intf->devid = devid;
            intf->bulk_rd.dir = USB_BULK_DIR_READ;
            intf->bulk_wr.dir = USB_BULK_DIR_WRITE;
            crate->intf_data = intf;

            crate->descr.snd_size = sizeof(t_ltr_crate_descr);

            crate->intf_func.wr_regs = f_intf_regs_write;
            crate->intf_func.rd_regs = f_intf_regs_read;
            crate->intf_func.ioctl = f_intf_ioctl;
            crate->intf_func.send = f_intf_send;
            crate->intf_func.wt_snd_rdy = f_intf_wait_send_rdy;
            crate->intf_func.close = f_intf_close;
            crate->intf_func.reopen = f_intf_reopen;

            /* добавляем в список используемых крейтов */
            f_usb_crates[f_usb_crates_cnt++] = intf;

            err = f_usb_start_open_dev(intf);
        } else {
            err = LTRSRV_ERR_MEMALLOC;
            free(crate);
            free(intf);
            port_intf->devid_free(devid);
        }
    }
    return err;
}


void p_usb_device_disconnected(const void *devid, const t_usb_port_intf *port_intf) {
    int idx = f_dev_get_idx(devid, port_intf);
    if (idx >= 0) {
        f_dev_disconnect(idx);
    }
}


void p_usb_check_devlist_finish(void) {
    int d;
    /* удаляем все не найденные устройства */
    for (d=f_usb_crates_cnt-1; d>=0; d--) {
        if (!f_usb_crates[d]->fnd && (f_usb_crates[d]->init_state!=USB_INIT_CLEANUP)) {
            f_dev_disconnect(d);
        }
    }
}


/* Очистка данных, используемых интерфейсом крейта, с удалением регистрации крейта
 * из программы. Если есть незавршенные трансферы, то очистка памяти будет только
 * запланирована на время завершения всех трансферов */
static void f_usb_free_data(t_usb_data* intf, t_usb_close_reasons reason) {
    /* удаляем крейт из сестемы, если зарегистрирован */
    if ((reason == USB_CLOSE_REASON_DISCONNECT) || (reason == USB_CLOSE_REASON_CRATE_REQ)) {
        f_usb_unregister_crate(intf);
    }

    if (intf->firm_file != NULL) {
        fclose(intf->firm_file);
        intf->firm_file = NULL;
    }

    intf->close_reason = reason;
    if (intf->init_state != USB_INIT_CLEANUP_DONE) {
        if (intf->init_state != USB_INIT_CLEANUP) {
            intf->close_init_state = intf->init_state; /* сохраняем для доп вывода в конце */
            intf->init_state = USB_INIT_CLEANUP;
        }
        f_check_cleanup(intf);
    } else {
        p_usb_cleanup_finish(intf);
    }
}



/* данная функция вызывает очистку устройства с предварительным увеличением
 * счетчика ошибок инициализации, который используется, чтобы прекратить
 * повторные попытки при достижении определенного порога */
void p_usb_close_dev_by_err(t_usb_data* intf, t_usb_close_reasons reason, int err, int syserr) {
    /* считаем только ошибки инициализации. если уже идет закрытие устройства,
     * то ошибки при этом повторно считать не нужно */
    if (!intf->close_reason) {
       f_set_dev_err(intf, err, syserr);
       f_usb_free_data(intf, reason);
    } else {
        f_check_cleanup(intf);
    }
}




int p_usb_process_ioctl(t_usb_data *intf, int transf_err, const uint8_t *reply,
                        unsigned actual_len) {
    int err = 0;

    intf->ctl.state = USB_TRANSF_STATE_FREE;

    if (intf->init_state==USB_INIT_CLEANUP) {
        f_check_cleanup(intf);
    } else if (!transf_err) {
        if (intf->crate->par.state == LTR_CRATE_CONSTATE_INITIALIZATION) {
            switch (intf->init_state) {
                case USB_INIT_GET_NAME:
                    memcpy(intf->crate->descr.devname, reply, actual_len);
                    err = f_usb_crate_init(intf->crate);
                    if (!err) {
                        if (intf->flags & USB_INIT_FLAGS_SWITCH_BOOTLOADER) {
                            err = intf->port_intf->start_ioctl(
                                        intf, LTR_USB_CMD_CALL_APPLICATION,
                                        LTR_CRATE_ADDR_BOOTLOADER_START,
                                        0, 0, NULL);
                            if (!err)
                                intf->init_state = USB_INIT_SWITCH_BOOTLOADER;
                        } else {
                            err = intf->port_intf->start_ioctl(
                                        intf, LTR_USB_CMD_GET_ARRAY,
                                        LTR_CRATE_ADDR_MODULE_DESCR,
                                        sizeof(t_ltr_device_raw_descr),
                                        0, NULL);
                            if (!err)
                                intf->init_state = USB_INIT_GET_DESCR;
                        }
                    }
                    break;
                case USB_INIT_SWITCH_BOOTLOADER:
                    err = intf->port_intf->start_ioctl(
                                intf, LTR_USB_CMD_CALL_APPLICATION,
                                LTR_CRATE_ADDR_FIRMWARE_START,
                                0, 0, NULL);
                    if (!err)
                        intf->init_state = USB_INIT_SWITCH_APPLICATION;
                    break;
                case USB_INIT_SWITCH_APPLICATION:
                    err = intf->port_intf->start_ioctl(
                                intf, LTR_USB_CMD_GET_ARRAY,
                                LTR_CRATE_ADDR_MODULE_DESCR,
                                sizeof(t_ltr_device_raw_descr), 0, NULL);
                    if (!err)
                        intf->init_state = USB_INIT_GET_DESCR;
                    break;
                case USB_INIT_GET_DESCR:
                    err = ltrsrv_crate_init_process_data(
                                intf->crate, LTR_CRATE_ADDR_MODULE_DESCR,
                                reply, actual_len);
                    if (!err) {
                        err = intf->port_intf->start_ioctl(
                                    intf, LTR_USB_CMD_GET_ARRAY,
                                    LTR_CRATE_ADDR_BOOTLOADER_VER,
                                    LTR_CRATE_REG_BOOTVER_VER_SIZE, 0, NULL);
                    }

                    if (!err)
                        intf->init_state = USB_INIT_GET_BOOTLOADER_VER;
                    break;
                case USB_INIT_GET_BOOTLOADER_VER:
                    err = ltrsrv_crate_init_process_data(
                                intf->crate, LTR_CRATE_ADDR_BOOTLOADER_VER,
                                reply, actual_len);
                    if (!err) {
                        err = intf->port_intf->start_ioctl(
                                    intf, LTR_USB_CMD_GET_ARRAY,
                                    LTR_CRATE_ADDR_FIRMWARE_VER,
                                    LTR_CRATE_REG_FIRMWARE_VER_SIZE, 0, NULL);
                    }
                    if (!err)
                        intf->init_state = USB_INIT_GET_FIRM_VER;
                    break;
                case USB_INIT_GET_FIRM_VER:
                    err = ltrsrv_crate_init_process_data(
                                intf->crate, LTR_CRATE_ADDR_FIRMWARE_VER,
                                reply, actual_len);
                    if (!err) {
                        if (intf->crate->descr_flags & LTR_CRATE_DESCR_FLAGS_SUPPORT_BOARD_REV) {
                            err = intf->port_intf->start_ioctl(
                                        intf, LTR_USB_CMD_GET_ARRAY,
                                        LTR_CRATE_ADDR_BOARD_REV,
                                        LTR_CRATE_REG_BOARD_REV_SIZE, 0, NULL);
                            if (!err)
                                intf->init_state = USB_INIT_GET_HARD_REV;
                        } else if (intf->crate->descr_flags & LTR_CRATE_DESCR_FLAGS_SUPPORT_GET_PRIMARY_IFACE) {
                            err = intf->port_intf->start_ioctl(
                                        intf, LTR_USB_CMD_GET_ARRAY,
                                        LTR_CRATE_ADDR_PRIMARY_IFACE,
                                        LTR_CRATE_REG_PRIMARY_IFACE_SIZE, 0, NULL);
                            if (!err)
                                intf->init_state = USB_INIT_GET_PRIMARY_IFACE;
                        } else {
                            err = f_usb_init_dmac(intf, 0, 0);
                            if (!err)
                                intf->init_state = USB_INIT_DMAC_INIT_0;
                        }
                    }
                    break;
                case USB_INIT_GET_HARD_REV:
                    err = ltrsrv_crate_init_process_data(
                                intf->crate, LTR_CRATE_ADDR_BOARD_REV,
                                reply, actual_len);
                    if (!err) {
                        if (intf->crate->descr_flags & LTR_CRATE_DESCR_FLAGS_SUPPORT_GET_PRIMARY_IFACE) {
                            err = intf->port_intf->start_ioctl(
                                        intf, LTR_USB_CMD_GET_ARRAY,
                                        LTR_CRATE_ADDR_PRIMARY_IFACE,
                                        LTR_CRATE_REG_PRIMARY_IFACE_SIZE, 0, NULL);
                            if (!err)
                                intf->init_state = USB_INIT_GET_PRIMARY_IFACE;
                        } else {
                            err = f_usb_init_dmac(intf, 0, 0);
                            if (!err)
                                intf->init_state = USB_INIT_DMAC_INIT_0;
                        }
                    }
                    break;
                case USB_INIT_GET_PRIMARY_IFACE:
                    if (actual_len < LTR_CRATE_REG_PRIMARY_IFACE_SIZE) {
                        err = LTRSRV_ERR_CRATE_PRIMARY_IFACE_SIZE;
                    } else {
                        t_ltr_crate_interface primary_iface = (t_ltr_crate_interface)reply[0];
                        ltrsrv_log_rec(primary_iface == LTR_CRATE_IFACE_USB ?
                                           LTRSRV_LOGLVL_INFO : LTRSRV_LOGLVL_WARN,
                                       intf->crate->log_str, 0, "Crate configured for %s primary interface",
                                       ltrsrv_log_get_crate_iface_str(primary_iface));
                        if (primary_iface!=LTR_CRATE_IFACE_USB) {
                            intf->crate->par.mode = LTR_CRATE_MODE_CONTROL;
                        }

                        err = f_usb_init_dmac(intf, 0, 0);
                        if (!err)
                            intf->init_state = USB_INIT_DMAC_INIT_0;
                    }
                    break;
                case USB_INIT_DMAC_INIT_0:
                    err = intf->port_intf->start_ioctl(
                                intf, LTR_USB_CMD_RESET_FPGA, 0, 0, 0, NULL);
                    if (!err)
                        intf->init_state = USB_INIT_FPGA_RESET;
                    break;
                case USB_INIT_FPGA_RESET:
                    if (intf->flags & USB_INTF_FLAGS_LOAD_FPGA) {
                        intf->firm_file = intf->port_intf->fopen(intf->firm_file_name, "rt");
                        if ( intf->firm_file==NULL) {
                            err = LTRSRV_ERR_CRATE_INVALID_FIRM_FILE;
                            ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, intf->crate->log_str, err,
                                           "Cannot open file %s", intf->firm_file_name);
                        } else {
                            intf->fpga_load_size = 0;
                            err = f_process_load_fpga(intf);
                        }
                        if (!err)
                            intf->init_state = USB_INIT_FPGA_LOAD;
                    } else {
                        err = intf->port_intf->start_ioctl(
                                    intf, LTR_USB_CMD_INIT_FPGA, 0, 0, 0, NULL);
                        if (!err)
                            intf->init_state = USB_INIT_FPGA_INIT;
                    }
                    break;
                case USB_INIT_FPGA_LOAD:
                    err = f_process_load_fpga(intf);
                    if (!err && intf->init_state==USB_INIT_FPGA_LOAD_DONE) {
                        err = intf->port_intf->start_ioctl(
                                    intf, LTR_USB_CMD_INIT_FPGA, 0, 0, 0, NULL);
                        if (!err)
                            intf->init_state = USB_INIT_FPGA_INIT;
                    }
                    break;
                case USB_INIT_FPGA_INIT:
                    err = f_usb_init_dmac(intf, 7, 0);
                    if (!err)
                        intf->init_state = USB_INIT_DMAC_INIT_1;
                    break;
                case USB_INIT_DMAC_INIT_1:
                    if (intf->crate->par.mode == LTR_CRATE_MODE_WORK) {
                        err = intf->port_intf->start_bulk(intf, &intf->bulk_rd,
                                                         LTRSRV_USB_READ_FPGA_INFO_TOUT);
                        if (!err)
                            intf->init_state = USB_INIT_FPGA_READ_INFO;
                    } else {
                        intf->init_state = USB_INIT_DONE;
                        intf->crate->modules_cnt = 0;
                        ltrsrv_crate_register(intf->crate);
                    }
                    break;
                default:
                    break;
            }

            if (err) {
                ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, intf->crate->log_str, err,
                               "control transfer processing error");
                p_usb_close_dev_by_err(intf, USB_CLOSE_REASON_INIT_PROCESS, err, LTRSRV_LAST_SYSERR);
            }
        } else if (intf->crate->par.state == LTR_CRATE_CONSTATE_WORK) {
            /* если нет указателя для сохранения принятого размера, то
               несовпадение принятого и переданного размера считаем ошибкой */
            if ((actual_len!=intf->proc_len) && (intf->proc_rcv_size==NULL)) {
                err = LTRSRV_ERR_CRATE_CMD_REPLY_LEN;
            } else {
                /* если c ответом есть данные => копируем в пользовательский буфер */
                if (intf->proc_buf && actual_len) {
                    memcpy(intf->proc_buf, reply, actual_len);
                }

                /* если есть указатель, куда нужно сохранить переданные данные,
                 * то используем его для сохранения актуального размера */
                if (intf->proc_rcv_size!=NULL)
                    *intf->proc_rcv_size = actual_len;
            }
            if (intf->proc_cmd_cb)
                intf->proc_cmd_cb(intf->crate, err, intf->proc_data);
        }
    } else {
         if (intf->init_state == USB_INIT_SWITCH_APPLICATION) {
            /* для LTR-U-8/16 мы явно переводим загрузчик в режим работы
             * приложения. Если этого не удалось => осталис в загрузчике */
            intf->init_state = USB_INIT_DONE;
            intf->crate->par.mode = LTR_CRATE_MODE_BOOTLOADER;
            intf->crate->modules_cnt = 0;
            ltrsrv_crate_register(intf->crate);
        } else {
            /* остальные ошибки считаем фатальными */
            err = transf_err;

            /* при ошибке в рабочем режиме, возвращаем ошибку наверхнем уровне.
             * а при инициализации - удаляем крейт сразу */
            if (intf->crate->par.state == LTR_CRATE_CONSTATE_WORK) {
                if (intf->proc_cmd_cb) {
                    intf->proc_cmd_cb(intf->crate, err, intf->proc_data);
                } else {
                    /* если есть cb, то вывод об ошибке будет в нем на более высоком
                     * уровне и нет смысла дублировать, поэтому выводим сообщение
                     * тут только при отсутствии cb */
                    ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, intf->crate->log_str, err,
                                   "control transfer failed");
                }
            } else {
                ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, intf->crate->log_str, err,
                               "Crate USB initialization stage '%s' control request failed",
                               LOGSTR_USB_INIT_STAGE(intf->init_state));
                p_usb_close_dev_by_err(intf, USB_CLOSE_REASON_INIT_CTL_FAILD, err, LTRSRV_LAST_SYSERR);
            }
        }
    }

    return err;
}


int p_usb_process_bulk_rd(t_usb_data *intf, int transf_err, int syserr, const uint8_t *data,
                          unsigned len) {
    int err = 0;

    intf->bulk_rd.transfs[intf->bulk_rd.get_pos].state = USB_TRANSF_STATE_FREE;
    if (++intf->bulk_rd.get_pos==LTRSRV_USB_BULK_MAX_TRANSF_CNT)
        intf->bulk_rd.get_pos = 0;

    if (intf->init_state==USB_INIT_CLEANUP) {
        f_check_cleanup(intf);
    } else {
        unsigned rem_len = len;
        uint32_t* transf_data = (uint32_t*)data;

        if (!transf_err) {
            /* во время инициализации по bulk нужно принять информацию о FPGA.
             * если сбор не был отсановлен, то информация может чередоваться
             * со словами данных от модулей. выделяем информацию по
             * байтам 0xE0 */
            if (intf->init_state == USB_INIT_FPGA_READ_INFO) {
                uint16_t *raw_data = (uint16_t*)data;
                if (!intf->fpga_info) {
                    intf->fpga_info = (char*)malloc(intf->fpga_info_req_size);
                    if (intf->fpga_info==NULL)
                        err = LTRSRV_ERR_MEMALLOC;
                }

                if (!err) {
                    unsigned i, drop_wrds=1;
                    for (i=0; (intf->fpga_cur_info_size < intf->fpga_info_req_size)
                         && (i < len/2); i++) {
                        /* 16-битные слова с информацией от ПЛИС состоят из байта 0xE0
                         * и собственно байта данных. Для LTR021 есть исключение -
                         * в нем информация заканчивается нулями, т.е. для него
                         * 0 тоже является действтиельным словом, если уже было хоть
                         * одно слово с информацией о ПЛИС. Флаг _INTF_FLAGS_SPECIAL_FPGA_DATA_PROC
                         * указывает, что такое дополнение 0 нужно обрабатывать */
                        if ((((raw_data[i] >> 8)&0xFF) == 0xE0) ||
                            ((intf->flags & USB_INTF_FLAGS_SPECIAL_FPGA_DATA_PROC)
                                && !raw_data[i]&& intf->fpga_cur_info_size)) {
                            intf->fpga_info[intf->fpga_cur_info_size++] =
                                    raw_data[i] & 0xFF;
                            drop_wrds = 0;
                        } else {
                            /* если до прихода информации о ПЛИС прошело сообщение
                             * о изменении состава модулей, то сохраняем этот факт,
                             * чтобы если что потом вручную его сгенерировать */
                            if (i < (len/2-3)) {
                                uint32_t* wrds = (uint32_t*)&raw_data[i];
                                if ((wrds[0]==LTR_CRATE_STREAM_EXCEPT_SIGN) &&
                                    (wrds[1]== (LTR_CRATE_STREAM_EXCEPT_SIGN2 |
                                                LTR_CRATE_STREAM_EXCEPT_MODULES_CH))) {
                                    intf->drop_exception = 1;
                                }
                            }
                        }
                    }

                    /* по завершению приема блока данных от FPGA - разбираем его
                     * и завершаем инициализацию */
                    if (intf->fpga_cur_info_size==intf->fpga_info_req_size) {
                        ltrsrv_crate_init_parse_fpga_data(intf->fpga_info,
                                                          &intf->crate->descr);
                        /* буфер с данными от ПЛИС больше не нужен => удаляем */
                        free(intf->fpga_info);
                        intf->fpga_info=NULL;

                        /* на случай, если в буфере есть и начало данных,
                         * переходим к данным за начальным блоком и обрабатываем
                         * этот остаток блока ниже */
                        rem_len = len-i*2;
                        transf_data = (uint32_t*)&data[i*2];

                        intf->init_state = USB_INIT_DONE;
                    } else {
                        rem_len = 0;
                        /* подсчитываем кол-во выброшенных трансферов до према
                         * действительных данных от ПЛИС, т.к. если сбор не был
                         * остановлен перед выдергиванием USB, то есть глюк
                         * что информация не приходит. Поэтому при достижении
                         * порога пробуем подключить крейт без этой информации */
                        if (drop_wrds) {
                            if (++intf->drop_init_wrds==USB_INIT_DROP_TRANSF_LIMIT) {
                                ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, intf->crate->log_str, 0,
                                               "no fpga data in input stream...");

                                intf->init_state = USB_INIT_DONE;
                                intf->drop_exception = 1;

                            } else {
                                err = intf->port_intf->start_bulk(intf, &intf->bulk_rd,
                                                                  LTRSRV_USB_READ_FPGA_INFO_TOUT);
                            }
                        } else {
                            err = intf->port_intf->start_bulk(intf, &intf->bulk_rd,
                                                              LTRSRV_USB_READ_FPGA_INFO_TOUT);
                        }
                    }
                }
            }
        } else if ((transf_err==LTRSRV_ERR_USB_TRANSF_TOUT) &&
                   (intf->init_state==USB_INIT_FPGA_READ_INFO)) {
            /*******************************************************************
             * Cлучай отсутствия информации о ПЛИС по bulk может означать несколько
             * ситуаций:
             *  - для LTR-EU - как правило значит что крейт настроен на Ethernet
             *    (крейт можно использовать только для настройки) или может возникать
             *    при неостановленном потоке (но тогда должен прийти хотя бы мусор)
             *  - для LTR-U-1 - нормальная ситуация для некоторых прошивок
             *  - в остальных случаях это фатальная ошибка
             *******************************************************************/
            if ((intf->crate->type==LTR_CRATE_TYPE_LTR030) ||
                    (intf->crate->type==LTR_CRATE_TYPE_LTR031)) {
                ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, intf->crate->log_str, 0,
                               "Can't receive fpga info. crate is configured for %s primary interface?",
                               ltrsrv_log_get_crate_iface_str(LTR_CRATE_IFACE_TCPIP));
                ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, intf->crate->log_str, 0,
                               "drop words = %d", intf->drop_init_wrds);

                /* если по bulk не было ничего => в управляющем режиме,
                 * если были какие-то левые слова => в обычном режиме, но
                 * не завершенным сбором */
                if (intf->drop_init_wrds) {
                    intf->drop_exception = 1;
                } else {
                    intf->crate->par.mode = LTR_CRATE_MODE_CONTROL;
                    intf->crate->modules_cnt = 0;
                }
                intf->init_state = USB_INIT_DONE;
            } else if (intf->crate->type==LTR_CRATE_TYPE_LTR021) {
                ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, intf->crate->log_str, 0,
                               "can't receive fpga info... may be ok for LTR-U-1");
                intf->init_state = USB_INIT_DONE;
            } else {
                err = LTRSRV_ERR_CRATE_FPGA_INFO_SIZE;
                ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, intf->crate->log_str, err, "Can't receive fpga info!");
            }
        } else if ((transf_err==LTRSRV_ERR_USB_TRANSF_TOUT)
                   && (intf->init_state==USB_INIT_DONE)) {
            /* в рабочем режиме таймаут на прием данных нормальная ситуация -
             * просто перезапускаем bulk */
            err = f_start_all_read(intf);
        } else {            
            err = transf_err;
        }

        /* после завершения инициализации передаем все данные наверх */
        if (!err && (intf->init_state == USB_INIT_DONE)) {
            if (intf->crate->par.state == LTR_CRATE_CONSTATE_INITIALIZATION) {
                ltrsrv_crate_register(intf->crate);
                if (intf->drop_exception) {
                    uint32_t words[2] = {LTR_CRATE_STREAM_EXCEPT_SIGN,
                                        LTR_CRATE_STREAM_EXCEPT_SIGN2 | LTR_CRATE_STREAM_EXCEPT_MODULES_CH};
                    ltrsrv_crate_rcv_done(intf->crate, words, 2);
                    intf->drop_exception = 0;
                }
            }

            if (rem_len) {
                err = ltrsrv_crate_rcv_done(intf->crate, transf_data,
                                            rem_len/sizeof(uint32_t));
            }

            if (!err)
                err = f_start_all_read(intf);
        }

        if (err) {            
            ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_MED, intf->crate->log_str,
                           0, "Bulk receive process faild (%d)", err);
            p_usb_close_dev_by_err(intf, USB_CLOSE_REASON_BULK_RECV_FAILD, err,
                                   syserr ? syserr : LTRSRV_LAST_SYSERR);
        }
    }

    return err;
}


int p_usb_process_bulk_wr(t_usb_data *intf, int transf_err, int syserr, uint8_t *data,
                          unsigned len) {
    int err = 0;

    intf->bulk_wr.transfs[intf->bulk_wr.get_pos].state = USB_TRANSF_STATE_FREE;
    if (++intf->bulk_wr.get_pos==LTRSRV_USB_BULK_MAX_TRANSF_CNT)
        intf->bulk_wr.get_pos = 0;



    if (intf->init_state==USB_INIT_CLEANUP) {
        f_check_cleanup(intf);
    } else if (!transf_err) {
        if (intf->snd_rdy_cb) {
            ltrsrv_wait_add_tmr(&intf->snd_rdy_cb, LTRSRV_WAITFLG_AUTODEL, 1, f_snd_rdy_cb, intf);
        }
    } else {
        err = transf_err;
    }

    if (err) {
        p_usb_close_dev_by_err(intf, USB_CLOSE_REASON_BULK_SEND_FAILD, err, syserr);
    }

    return err;
}



void p_usb_process_bulk_out_of_order(t_usb_data *intf, t_transf_params *transf, int transf_err, int syserr) {
   transf->state = USB_TRANSF_STATE_FREE;
   p_usb_close_dev_by_err(intf, USB_CLOSE_REASON_BULK_OUT_OF_ORDER,
                          transf_err ? transf_err : LTRSRV_ERR_USB_TRANSF_OUT_OF_ORDER, syserr);

}


int p_usb_close(void) {
    int i;
    /* отменяем инициализацию всех крейтов, которые не дошли до рабочего состояния */
    for (i=f_usb_crates_cnt-1; i >= 0 ; i--) {
        f_usb_free_data(f_usb_crates[i], USB_CLOSE_REASON_DISCONNECT);
    }
    return 0;
}
