/*****************************************************************************
  Файл содержит функции специфичные для загрузки через usb-интерфейс
  Реалезована как для Windows (через драйвер L-Card),
      так и для Linux (через libusb-1.0)
  Загрузчик для любого устройства должен иметь одинаковые PID/VID.
  Тип устройства узнается с помощью запроса на получение информации
    о устройстве
  ****************************************************************************/


#include "lboot.h"
#include "lboot_usb.h"
#include <stdio.h>
#include <string.h>
#include "getopt.h"

#define LBOOT_USB_MAX_BLOCK_SIZE  1024


static int f_parse_option(int opt);
static const char* f_get_opt_descr(void);
static int f_init(void);
static int f_cleanup(void);
static int f_usb_open(const char* devname, const char* serial, const void* params,
                   t_lboot_devinfo* info, int *out);
static int f_get_devmode(uint8_t* mode);
static int f_get_bootloader_version(uint16_t* ver);
static int f_get_dev_flags(uint32_t* flags);
static int f_get_features(uint32_t* flags);
static int f_start_write(t_lboot_startwr_info wr_info);
static int f_write(const uint8_t* buf, uint32_t size);
static int f_write_sign(const uint8_t* sign, uint32_t size);
static int f_speccmd(const t_lboot_speccmd_info* cmd_info, uint32_t size);
static int f_close(void);
static int f_app_to_boot(int *out);

typedef enum {
    USB_HANDLE_INVALID = 0,
#ifdef ENABLE_USB_LIBUSB
    USB_HANDLE_LIBUSB = 1,
#endif
#ifdef ENABLE_USB_LCOMP
    USB_HANDLE_LCOMP_OLD=2,
    USB_HANDLE_LCOMP_NEW=3
#endif
} t_usb_handle_type;







#ifdef ENABLE_USB_LCOMP
    #ifndef WIN32_LEAN_AND_MEAN
        #define WIN32_LEAN_AND_MEAN
    #endif

    #include <Windows.h>
    #include <winioctl.h>
    #include "ioctl.h"
    #include "tchar.h"

    /* запрос для старого драйвера */
    #define DIOC_SEND_COMMAND_OLD \
        CTL_CODE (FILE_DEVICE_UNKNOWN,0x4,METHOD_OUT_DIRECT,FILE_ANY_ACCESS)

    static unsigned f_app_lcomp_id = 90;
    static char f_app_lcomp_devname[LBOOT_DEVNAME_SIZE];
#endif

#ifdef ENABLE_USB_LIBUSB
    #include "libusb-1.0/libusb.h"
    static unsigned f_app_dev_id = 0xCA01 ;
#endif

static unsigned f_app_req_boot = 0xF;

static int f_app_usb_type_flags;

#define LBOOT_USBREQ_GET_MODULE_INFO     0x80
#define LBOOT_USBREQ_GET_MODULE_MODE     0x81
#define LBOOT_USBREQ_GET_LAST_ERROR      0x82
#define LBOOT_USBREQ_START_WRITE_FIRM    0x40
#define LBOOT_USBREQ_WRITE_FIRM          0x41
#define LBOOT_USBREQ_WRITE_SIGN          0x42
#define LBOOT_USBREQ_GET_BOOTLDR_VERS    0x43
#define LBOOT_USBREQ_GET_FLAGS           0x44
#define LBOOT_USBREQ_SPECCMD             0x45
#define LBOOT_USBREQ_GET_FEATURES        0x46
#define LBOOT_USBREQ_GET_MODULE_NAME     11

#define LBOOT_USB_VID       0x0471
#define LBOOT_USB_VID_NEW   0x2A52
#define LBOOT_USB_PID       0xEFFF


#ifdef ENABLE_USB_LCOMP

    #define LBOOT_USB_DEVICE_ID 0xEFFF

    typedef struct {
        ULONG Base;
        ULONG BaseL;
        ULONG Base1;
        ULONG BaseL1;
        ULONG Mem;
        ULONG MemL;
        ULONG Mem1;
        ULONG MemL1;
        ULONG Irq;
        ULONG DeviceID;
        ULONG DSPType;
        ULONG Dma;
        ULONG DmaDac;
        ULONG DTA_REG;
        ULONG IDMA_REG;
        ULONG CMD_REG;
        ULONG IRQ_RST;
        ULONG DTA_ARRAY;
        ULONG RDY_REG;
        ULONG CFG_REG;
    } DEVICE_INITIAL_INFO;
#endif


//структура стандартного запроса к модулю
struct st_lreq {
    uint32_t CmdCode;
    uint32_t Param;
    uint32_t TxLength;
    uint32_t RxLength;
};
typedef struct st_lreq t_lreq;

typedef struct {
    t_usb_handle_type type;
    union {
#ifdef ENABLE_USB_LIBUSB
        libusb_device_handle* libusb_hnd;
#endif
#ifdef ENABLE_USB_LCOMP
        HANDLE lcomp_hnd;
#endif
    };
} t_usb_handle;


static int f_ioctl_tout = 300;
static t_usb_handle f_hnd;

static int f_ioreq(t_usb_handle handle, const t_lreq* req,
                   const void* snd_data, void* rcv_data, uint32_t* rx_size);


/** Описание опций интерфейса Modbus */
static const char* f_opt_descr =
"usb app-to-boot options:\n"\
"    --usb-app-boot-req=req-code  - vendor request for switch from application\n"\
"                                    to bootloader. Required parameter for \n"\
"                                    --app-to-boot option\n"\
"    --usb-app-lcomp-id=id        - application device ID in lcomp inf-file.\n"\
"                                    Required for --app-to-boot option for\n"\
"                                    device with current lcomp driver\n"
"    --usb-app-lcomp-devname      - application device name old lcomp devices.\n"
"                                    Required for --app-to-boot option for\n"\
"                                    device with OLD lcomp driver\n"
"    --usb-app-dev-id=id          - application USB Product-ID. Required for\n"\
"                                    --app-to-boot option for device whith \n"\
"                                    libusb\n";


const t_app_iface g_usb_app_iface = {
    { "usb",
      "Vendor control request over USB interface",
      NULL,
      NULL,
      f_parse_option,
      f_get_opt_descr,
      NULL,
    },
    f_app_to_boot
};


/** структура с информацией о модуле USB */
const t_boot_iface g_usb_intf_info = {
    { "usb",
      "Custom protocol over USB interface",
      f_init,
      f_cleanup,
      NULL,
      NULL,
      NULL,
    },
    &g_usb_app_iface,
    LBOOT_USB_MAX_BLOCK_SIZE,
    f_usb_open,
    f_get_devmode,
    f_get_bootloader_version,
    f_get_dev_flags,
    f_get_features,
    f_start_write,
    lboot_fw_transfer_std_block,
    f_write,
    f_write_sign,
    f_speccmd,
    f_close
};




static int f_parse_option(int opt) {
    int err = 0;
    switch (opt) {
        case LBOOT_OPT_USB_APP_BOOT_REQ:
            sscanf(optarg, "%i", &f_app_req_boot);
            break;
#ifdef ENABLE_USB_LCOMP
        case LBOOT_OPT_USB_APP_LCOMP_ID:
            sscanf(optarg, "%i", &f_app_lcomp_id);
            /* наличие данной опции указывает, что нужно использовать
             * новый драйвер lcomp, чтобы перейти в режим загрузчика */
            f_app_usb_type_flags |= (1 << USB_HANDLE_LCOMP_NEW);
            break;
        case LBOOT_OPT_USB_APP_LCOMP_DEVNAME:
            strncpy(f_app_lcomp_devname, optarg, sizeof(f_app_lcomp_devname));
            f_app_lcomp_devname[sizeof(f_app_lcomp_devname)-1] = '\0';
            /* наличие данной опции указывает, что нужно использовать
             * старый драйвер lcomp, чтобы перейти в режим загрузчика */
            f_app_usb_type_flags |= (1 << USB_HANDLE_LCOMP_OLD);
            break;
#endif
#ifdef ENABLE_USB_LIBUSB
        case LBOOT_OPT_USB_APP_DEV_ID:
            sscanf(optarg, "%i", &f_app_dev_id);
            f_app_usb_type_flags |= (1 << USB_HANDLE_LIBUSB);
            break;
#endif
        default:
            err = LBOOT_ERR_INVALID_OPTION;
            break;
    }
    return err;
}

static const char* f_get_opt_descr(void) {
    return f_opt_descr;
}

static int f_init(void) {
#ifdef ENABLE_USB_LIBUSB
    libusb_init(NULL);
#endif
    return 0;
}

static int f_cleanup(void) {
#ifdef ENABLE_USB_LIBUSB
    libusb_exit(NULL);
#endif
    return 0;
}


#ifdef ENABLE_USB_LCOMP

/*************************************************************************
    Посылка управляющего запроса драйверу для передачи
    команд по Control Pipe Usb
**************************************************************************/
static int32_t f_ioctl_lcomp(HANDLE hDevice,
                        uint32_t dwIoControlCode,        // control code of operation to perform
                        void* lpInBuffer,            // pointer to buffer to supply input data
                        uint32_t nInBufferSize,            // size of input buffer in bytes
                        const void* lpOutBuffer,            // pointer to buffer to receive output data
                        uint32_t nOutBufferSize,        // size of output buffer in bytes
                        uint32_t* rx_size,
                        uint32_t TimeOut) {

    uint32_t RealBytesTransferred;
    uint32_t BytesReturned;
    OVERLAPPED Ov;
    int32_t err = 0;


    // инициализируем OVERLAPPED структуру
    memset(&Ov, 0x0, sizeof(OVERLAPPED));
    // создаём событие для асинхронного запроса
    Ov.hEvent = CreateEvent(NULL, FALSE , FALSE, NULL);
    if(!Ov.hEvent) {
        err = LBOOT_ERR_ALLOC_RESOURCE;
    } else {
        // посылаем требуемый запрос
        if(!DeviceIoControl(    hDevice, dwIoControlCode,
                                lpInBuffer, nInBufferSize,
                                (void*)lpOutBuffer, nOutBufferSize,
                                &BytesReturned, &Ov)) {
            uint32_t err = GetLastError();
            if(err != ERROR_IO_PENDING) {
                CloseHandle(Ov.hEvent);
                err =  LBOOT_ERR_IOCTRL_FAILED;
                GetOverlappedResult(hDevice, &Ov, &RealBytesTransferred, TRUE);
            }
        }
    }

    if (!err) {
        // ждём окончания выполнения запроса
        if (WaitForSingleObject(Ov.hEvent, TimeOut) == WAIT_TIMEOUT) {
            CancelIo(hDevice);
            CloseHandle(Ov.hEvent);
            err = LBOOT_ERR_IOCTRL_FAILED;
            GetOverlappedResult(hDevice, &Ov, &RealBytesTransferred, TRUE);
        }
    }
    // попробуем получить кол-во реально переданных байт данных
    if (!err) {
        if(!GetOverlappedResult(hDevice, &Ov, &RealBytesTransferred, TRUE)) {
            CancelIo(hDevice);
            CloseHandle(Ov.hEvent);
            err = LBOOT_ERR_IOCTRL_FAILED;
        } else if(nOutBufferSize != RealBytesTransferred) {
            CancelIo(hDevice);
            CloseHandle(Ov.hEvent);
        } else {
            CloseHandle(Ov.hEvent);
        }
    }

    if (!err && (rx_size!=NULL)) {
        *rx_size = RealBytesTransferred;
    }

    return err;
}

/* Открытие устройства с заданным ID для нового драйвера LCOMP */
static t_usb_handle f_slot_open_lcomp(int slot, int id) {
    HANDLE hDevice;
    DEVICE_INITIAL_INFO info;
    t_usb_handle hnd;
    int err = 0;
    char DeviceName[18];

    /* пробуем открыть устройство в i-ом виртуальном слоте */
    sprintf(DeviceName, "\\\\.\\LDev%d", slot);

    hDevice = CreateFileA(DeviceName, GENERIC_WRITE |GENERIC_WRITE , 0, NULL,
                            OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (hDevice!=INVALID_HANDLE_VALUE) {
        int rx_size = 0;
        /* получаем стандартные параметры для устройства (из реестра) */
        err = f_ioctl_lcomp(hDevice, DIOC_GET_PARAMS, NULL, 0,
                            &info, sizeof(info), &rx_size, f_ioctl_tout);
        /* проверяем ID */
        if (!err) {
            if (info.DeviceID != id) {
                CloseHandle(hDevice);
                err = -1;
            }
        }
    } else {
        err = -2;
    }

    if (!err) {
        hnd.type = USB_HANDLE_LCOMP_NEW;
        hnd.lcomp_hnd = hDevice;
    } else {
        hnd.type = USB_HANDLE_INVALID;
    }

    return hnd;
}

/* Открытие устройства с заданным именем для СТАРОГО драйвера LCOMP */
static t_usb_handle f_slot_open_lcomp_old(int slot, const char *devname) {
    int err = 0;
    t_usb_handle hnd;
    char DeviceName[18];

    /* пробуем открыть устройство в i-ом виртуальном слоте */
    sprintf(DeviceName, "\\\\.\\LDevUsb%d", slot);

    hnd.type = USB_HANDLE_LCOMP_OLD;
    hnd.lcomp_hnd = CreateFileA(DeviceName, GENERIC_WRITE |GENERIC_WRITE , 0, NULL,
                            OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (hnd.lcomp_hnd != INVALID_HANDLE_VALUE) {
        /* получаем название устройства */
        t_lreq req = {LBOOT_USBREQ_GET_MODULE_NAME, 0, 0, LBOOT_DEVNAME_SIZE};
        uint32_t rx_size = 0;
        char name[LBOOT_DEVNAME_SIZE];
        err = f_ioreq(hnd, &req, NULL, name, &rx_size);

        if (!err) {
            if (strncmp(devname, name,LBOOT_DEVNAME_SIZE)) {
                CloseHandle(hnd.lcomp_hnd);
                err = -1;
            }
        }        
    } else {
        err = -2;
    }

    if (err) {
        hnd.type = USB_HANDLE_INVALID;
    }

    return hnd;
}

#endif

static int f_ioreq(t_usb_handle handle, const t_lreq* req,
                   const void* snd_data, void* rcv_data, uint32_t* rx_size) {
    int err = 0;
#ifdef ENABLE_USB_LCOMP
    if ((handle.type == USB_HANDLE_LCOMP_NEW)
            || (handle.type == USB_HANDLE_LCOMP_OLD)) {
        uint16_t usbreq[4];
        if (req->RxLength > 0) {
            usbreq[0] = 1;
        } else {
            usbreq[0] = 0;
        }

        if (!err) {
            /* заполняем последующие 3 слова запроса к драйверу */
            usbreq[1] = req->CmdCode & 0xFF;
            usbreq[2] = req->Param & 0xFFFF;
            usbreq[3] = (req->Param >> 16) & 0xFFFF;

            /* посылаем запрос */
            err = f_ioctl_lcomp(handle.lcomp_hnd,
                                handle.type == USB_HANDLE_LCOMP_OLD ?
                                    (DIOC_SEND_COMMAND_OLD) : (DIOC_SEND_COMMAND),
                                usbreq, sizeof(usbreq),
                                usbreq[0] ? rcv_data : snd_data,
                                usbreq[0] ? req->RxLength : req->TxLength,
                                rx_size,
                                f_ioctl_tout);

            /* при неудаче - пробуем получить код ошибки устройства
                послав ему запрос LBOOT_USBREQ_GET_LAST_ERROR */
            if (err) {
                int32_t eres;
                int32_t devres;
                uint32_t errs_rx_size =0;

                usbreq[0] = 1;
                usbreq[1] = LBOOT_USBREQ_GET_LAST_ERROR;
                usbreq[2] = usbreq[3] = 0;

                eres = f_ioctl_lcomp(handle.lcomp_hnd,
                                    handle.type == USB_HANDLE_LCOMP_OLD ?
                                         (DIOC_SEND_COMMAND_OLD) : (DIOC_SEND_COMMAND),
                                    usbreq, sizeof(usbreq),
                                    &devres, sizeof(devres),
                                    &errs_rx_size, f_ioctl_tout);
                /* если успешно получили код ошибки устройства - то возвращаем его в качестве результата */
                if (!eres
                    && (errs_rx_size == sizeof(devres))
                    && (devres !=0)) {
                    err = devres;
                }
            }
        }
    }
#endif
#ifdef ENABLE_USB_LIBUSB
    if (handle.type == USB_HANDLE_LIBUSB) {
        uint8_t req_type = LIBUSB_REQUEST_TYPE_VENDOR;
        uint16_t len;
        uint8_t* iobuf;
        int usbres = 0;

        if (req->RxLength > 0) {
            req_type |= LIBUSB_ENDPOINT_IN;
            len = req->RxLength & 0xFFFF;
            iobuf = rcv_data;
        } else {
            req_type |= LIBUSB_ENDPOINT_OUT;
            len = req->TxLength & 0xFFFF;
            iobuf = (uint8_t*)snd_data;
        }

        if (!err) {
            usbres = libusb_control_transfer(handle.libusb_hnd,
                    req_type, req->CmdCode & 0xFF,
                    req->Param & 0xFFFF,
                    (req->Param >> 16) & 0xFFFF,
                    iobuf, len, f_ioctl_tout);

            if (usbres < 0) {
                err = LBOOT_ERR_IOCTRL_FAILED;
            } else if (rx_size) {
                *rx_size = usbres;
            }

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

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

static void f_close_hnd(t_usb_handle hnd) {
#ifdef ENABLE_USB_LCOMP
    if ((hnd.type == USB_HANDLE_LCOMP_NEW) || (hnd.type == USB_HANDLE_LCOMP_OLD)) {
        CloseHandle(hnd.lcomp_hnd);
    }
#endif
#ifdef ENABLE_USB_LIBUSB
    if (hnd.type == USB_HANDLE_LIBUSB)
        libusb_close(hnd.libusb_hnd);
#endif
    hnd.type = USB_HANDLE_INVALID;
}

static int f_usb_check_dev(t_usb_handle hnd, const char *devname, const char *serial,
                           t_lboot_devinfo *info) {
    int err = 0;
    uint32_t rx_size = 0;
    /* получаем информацию о устройстве */
    t_lreq req = {LBOOT_USBREQ_GET_MODULE_INFO, 0, 0, sizeof(t_lboot_devinfo)};
    err = f_ioreq(hnd, &req, NULL, info, &rx_size);
    if (!err && (rx_size != sizeof(t_lboot_devinfo)))
        err = LBOOT_ERR_IOCTRL_FAILED;
    /* если заданы имя устройства и серийный - проверяем совпадение */
    if (!err && (devname != NULL) && strcmp(devname, info->devname))
        err = LBOOT_ERR_INVALID_DEVNAME;
    if (!err && (serial != NULL) && strcmp(serial, info->serial))
        err = LBOOT_ERR_INVALID_SERIAL;
    return err;
}



static int f_usb_open(const char *devname, const char *serial, const void *params,
                        t_lboot_devinfo *info, int *out) {
    int err = LBOOT_ERR_DEVICE_NOT_FOUND;
#ifdef ENABLE_USB_LCOMP
    int slot;
    t_usb_handle hnd;
    /* ищем сперва наше устройство среди устройств из нового драйвера */
    for (slot = 0; (slot < 255) && (err == LBOOT_ERR_DEVICE_NOT_FOUND); slot++) {
        /*  пробуем открыть устройство.
            у всей устройств в режиме загрузчика одинаковый ID */
        hnd = f_slot_open_lcomp(slot, LBOOT_USB_DEVICE_ID);
        if (hnd.type != USB_HANDLE_INVALID) {
            /* если нашли подходящее устройство - выходим без ошибок */
            if (f_usb_check_dev(hnd, devname, serial, info) == 0) {
                err = 0;
                f_hnd = hnd;
            } else {
                CloseHandle(hnd.lcomp_hnd);
            }
        }
    }

    /* если не нашли среди новых устройств - ищем среди старых */
    if (err == LBOOT_ERR_DEVICE_NOT_FOUND) {
        for (slot = 0; (slot < 255) && (err == LBOOT_ERR_DEVICE_NOT_FOUND); slot++) {
            /*  пробуем открыть устройство.
                у всей устройств в режиме загрузчика одинаковый ID */
            hnd = f_slot_open_lcomp_old(slot, "LBOOT");
            if (hnd.type != USB_HANDLE_INVALID) {
                /* если нашли подходящее устройство - выходим без ошибок */
                if (f_usb_check_dev(hnd, devname, serial, info) == 0) {
                    err = 0;
                    f_hnd = hnd;
                } else {
                    CloseHandle(hnd.lcomp_hnd);
                }
            }
        }
    }
#endif
#ifdef ENABLE_USB_LIBUSB
    if (err == LBOOT_ERR_DEVICE_NOT_FOUND) {
        libusb_device **list;
        ssize_t size, i;
        int32_t cur_err = 0;


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


            if (libusb_get_device_descriptor (list[i], &devdescr) == 0) {
                //сравниваем VID, PID с индентификаторами загрузочного устройства
                if (((devdescr.idVendor == LBOOT_USB_VID) || (devdescr.idVendor == LBOOT_USB_VID_NEW))
                        && (devdescr.idProduct == LBOOT_USB_PID)) {
                    cur_err = libusb_open(list[i], &usbhnd);
                    if (!cur_err) {
                        t_usb_handle hnd;
                        hnd.type = USB_HANDLE_LIBUSB;
                        hnd.libusb_hnd = usbhnd;

                        /* если нашли подходящее устройство - выходим без ошибок */
                        if (f_usb_check_dev(hnd, devname, serial, info) == 0) {
                            err = 0;
                            f_hnd = hnd;
                        } else {
                            libusb_close(usbhnd);
                        }
                    }
                }
            }
        }
        libusb_free_device_list(list, 1);
    }
#endif

    if (!err) {
        /* увеличиваем таймаут, так некоторые операции требует длительного выполнения */
        f_ioctl_tout = 20000;
    }
    return err;
}



int f_get_devmode(uint8_t* mode) {
    int err = 0;
    t_lreq req = {LBOOT_USBREQ_GET_MODULE_MODE, 0, 0, sizeof(*mode)};
    uint32_t rx_size = 0;
    err = f_ioreq(f_hnd, &req, NULL, mode, &rx_size);
    if (rx_size != sizeof(*mode))
        err = LBOOT_ERR_IOCTRL_FAILED;
    return err;
}




static int f_get_bootloader_version(uint16_t* ver) {
    int err = 0;
    t_lreq req = {LBOOT_USBREQ_GET_BOOTLDR_VERS, 0, 0, sizeof(*ver)};
    uint32_t rx_size = 0;
    err = f_ioreq(f_hnd, &req, NULL, ver, &rx_size);
    if (rx_size != sizeof(*ver))
        err = LBOOT_ERR_IOCTRL_FAILED;
    return err;
}

static int f_get_dev_flags(uint32_t* flags) {
    int err = 0;
    t_lreq req = {LBOOT_USBREQ_GET_FLAGS, 0, 0, sizeof(*flags)};
    uint32_t rx_size = 0;
    err = f_ioreq(f_hnd, &req, NULL, flags, &rx_size);
    if (rx_size != sizeof(*flags))
        err = LBOOT_ERR_IOCTRL_FAILED;
    return err;
}

static int f_get_features(uint32_t* features) {
    int err = 0;
    t_lreq req = {LBOOT_USBREQ_GET_FEATURES, 0, 0, sizeof(*features)};
    uint32_t rx_size = 0;
    err = f_ioreq(f_hnd, &req, NULL, features, &rx_size);
    if (rx_size != sizeof(*features))
        err = LBOOT_ERR_IOCTRL_FAILED;
    return err;
}

static int f_speccmd(const t_lboot_speccmd_info* cmd_info, uint32_t size) {
    t_lreq req = {LBOOT_USBREQ_SPECCMD, 0, size, 0};
    return f_ioreq(f_hnd, &req, cmd_info, 0, NULL);
}

static int f_start_write(t_lboot_startwr_info wr_info) {
    t_lreq req = {LBOOT_USBREQ_START_WRITE_FIRM, 0, sizeof(wr_info), 0};
    return f_ioreq(f_hnd, &req, &wr_info, 0, NULL);
}

static int f_write(const uint8_t* buf, uint32_t size) {
    t_lreq req = {LBOOT_USBREQ_WRITE_FIRM, 0, size, 0};
    return f_ioreq(f_hnd, &req, buf, NULL, NULL);
}

static int f_write_sign(const uint8_t* sign, uint32_t size) {
    t_lreq req = {LBOOT_USBREQ_WRITE_SIGN, 0, size, 0};
    return f_ioreq(f_hnd, &req, sign, NULL, NULL);
}

int f_close(void) {
    f_close_hnd(f_hnd);
    return 0;
}

/* функция переводит в загрузчик заданное устройство */
static int f_usb_app_to_boot_req(t_usb_handle hnd) {
    t_lreq req = {f_app_req_boot, 0, 0, 0};
    return f_ioreq(hnd, &req, NULL, NULL, NULL) == 0 ? 0 : LBOOT_ERR_IOCTRL_FAILED;
}

static int f_app_to_boot(int *out) {
    int err = LBOOT_ERR_DEVICE_NOT_FOUND;
    t_usb_handle hnd;
    hnd.type = USB_HANDLE_INVALID;
#ifdef ENABLE_USB_LCOMP
    if (f_app_usb_type_flags & (1 << USB_HANDLE_LCOMP_NEW)) {
        int slot;
        for (slot = 0; (slot < 255) && (hnd.type == USB_HANDLE_INVALID); slot++) {
            /*  пробуем открыть устройство.
                у всей устройств в режиме загрузчика одинаковый ID */
            hnd = f_slot_open_lcomp(slot, f_app_lcomp_id);
        }
    }

    if ((f_app_usb_type_flags & (1 << USB_HANDLE_LCOMP_OLD)) && (hnd.type == USB_HANDLE_INVALID)) {
        int slot;
        for (slot = 0; (slot < 255) && (hnd.type == USB_HANDLE_INVALID); slot++) {
            /*  пробуем открыть устройство. */
            hnd = f_slot_open_lcomp_old(slot, f_app_lcomp_devname);
        }
    }
#endif
#ifdef ENABLE_USB_LIBUSB
    if ((f_app_usb_type_flags & (1 << USB_HANDLE_LIBUSB)) && (hnd.type == USB_HANDLE_INVALID)) {
        libusb_device **list;
        ssize_t size, i;

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

            if (libusb_get_device_descriptor (list[i], &devdescr) == 0) {
                /* сравниваем VID, PID с индентификаторами загрузочного устройства */
                if (((devdescr.idVendor == LBOOT_USB_VID) ||
                     (devdescr.idVendor == LBOOT_USB_VID_NEW))
                        && (devdescr.idProduct == f_app_dev_id)) {
                    int32_t cur_err = libusb_open(list[i], &usbhnd);
                    if (!cur_err) {
                        hnd.type = USB_HANDLE_LIBUSB;
                        hnd.libusb_hnd = usbhnd;
                    }
                }
            }
        }
        libusb_free_device_list(list, 1);
    }

#endif

    if (hnd.type != USB_HANDLE_INVALID) {
        /* переход в загрузчик */
        err = f_usb_app_to_boot_req(hnd);
        f_close_hnd(hnd);
    }

    return err;
}
