#include "ltr030api.h"

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

#include "crc.h"

typedef enum LTR030_USB_CMD {
    LTR030_USB_CMD_WRITE_DSP_FLASH =   9,
    LTR030_USB_CMD_READ_DSP_FLASH  =  10,
    LTR030_USB_CMD_RESET           =  12
} t_ltr030_usb_cmds;

#define LTR030_USB_MAX_CONTROL_LEN      4096

#define LTR030_FLASH_CONFIG_SIGN        0x434C
#define LTR030_FLASH_CONFIG_CRC_SIZE    2
#define LTR030_FLASH_CONFIG_SIZE_MAX    0x000200UL

#define LTR030_FLASH_FABRIC_INFO_SIZE   0x000200UL

#define LTR030_FLASH_ADDR_BOOTLOADER    0x000000UL
#define LTR030_FLASH_ADDR_FABRIC_INFO   0x020000UL
#define LTR030_FLASH_ADDR_SERIAL        0x020000UL
#define LTR030_FLASH_ADDR_DSP_FIRM      0x020200UL
#define LTR030_FLASH_ADDR_CONFIG2_MAIN  0x1E6A00UL
#define LTR030_FLASH_ADDR_CONFIG2_SEC   0x1E6C00UL
#define LTR030_FLASH_ADDR_CONFIG        0x1E6E00UL
#define LTR030_FLASH_ADDR_MAC_OLD       0x1E6E0EUL
#define LTR030_FLASH_ADDR_FPGA_INFO     0x1E7000UL
#define LTR030_FLASH_ADDR_FPGA_FIRM     0x1E7008UL

#define LTR030_FLASH_SIZE               0x200000UL

#define LTR030_FLASH_FPGA_FRIM_MAX_SIZE  (LTR030_FLASH_SIZE - LTR030_FLASH_ADDR_FPGA_FIRM)
#define LTR030_FLASH_DSP_FIRM_MAX_SIZE  (LTR030_FLASH_ADDR_CONFIG-LTR030_FLASH_ADDR_DSP_FIRM)

#define LTR030_FPGA_INFO_SIZE          8
#define LTR030_CONFIG_SIZE             14



#define LTR030_CONFIG_INTF_USB          0
#define LTR030_CONFIG_INTF_ETH          1

#pragma pack(1)

typedef struct {
    WORD   Sign;
    WORD   Size;
} t_ltr030_flash_cfg_v2_hdr;


typedef struct {
    t_ltr030_flash_cfg_v2_hdr hdr;
    WORD   device_mode;
    BYTE   host_addr[4];
    BYTE   gateway_addr[4];
    BYTE   netmask[4];
    BYTE   mac_addr[6];
} t_ltr030_flash_cfg_v2;

typedef struct {
    BYTE serial[16];
    BYTE mac[6];
    BYTE reserv[512 - 16 - 6 - 2];
    WORD crc;
} t_ltr030_fabric_info;

#pragma pack()


static const TLTR_ERROR_STRING_DEF f_err_str_tbl[] = {
    {LTR030_ERR_UNSUPORTED_CRATE_TYPE, "Данный тип крейта не поддерживается библиотекой"},
    {LTR030_ERR_FIRM_VERIFY,           "Ошибка проверки правильности записи прошивки"},
    {LTR030_ERR_FIRM_SIZE,             "Неверный размер прошивки"}
};


static INT f_flash_cfg_v2_check_hdr(const t_ltr030_flash_cfg_v2_hdr *hdr) {
    if (hdr->Sign != LTR030_FLASH_CONFIG_SIGN)
        return LTR_ERROR_FLASH_INFO_NOT_PRESENT;
    if ((hdr->Size < ((offsetof(t_ltr030_flash_cfg_v2, mac_addr)) + 6)) ||
            (hdr->Size > LTR030_FLASH_CONFIG_SIZE_MAX))
       return LTR_ERROR_FLASH_INFO_NOT_PRESENT;
    return LTR_OK;
}

static INT f_flash_cfg_v2_check_crc(const BYTE* cfg_arr, WORD size) {
    WORD crc = cfg_arr[size] | ((WORD)cfg_arr[size + 1] << 8);
    WORD calc_crc = eval_crc16 (0xFFFFu, cfg_arr, size);
    return (crc != calc_crc) ? LTR_ERROR_FLASH_INFO_CRC : LTR_OK;
}

static WORD f_get_iface(WORD intf_code) {
    return intf_code == LTR030_CONFIG_INTF_ETH ? LTR_CRATE_IFACE_TCPIP :
           intf_code == LTR030_CONFIG_INTF_USB ? LTR_CRATE_IFACE_USB :
                                                 LTR_CRATE_IFACE_UNKNOWN;
}

static WORD f_set_iface(WORD iface) {
    return iface == LTR_CRATE_IFACE_TCPIP ? LTR030_CONFIG_INTF_ETH : LTR030_CONFIG_INTF_USB;
}

static INT f_flash_cfg_v2_read(TLTR030 *hnd, DWORD addr, t_ltr030_flash_cfg_v2 *cfg) {
    t_ltr030_flash_cfg_v2_hdr hdr;
    INT err = LTR030_FlashRead(hnd, addr, (BYTE *)&hdr, sizeof(hdr));
    if (err == LTR_OK) {
        err = f_flash_cfg_v2_check_hdr(&hdr);
    }

    if (err == LTR_OK) {
        BYTE *cfg_arr = malloc(hdr.Size  + LTR030_FLASH_CONFIG_CRC_SIZE);
        if (cfg_arr == NULL) {
            err = LTR_ERROR_MEMORY_ALLOC;
        } else {
            DWORD hdr_size = sizeof(hdr);
            memcpy(cfg_arr, &hdr, sizeof(hdr));
            err = LTR030_FlashRead(hnd, addr + hdr_size, &cfg_arr[hdr_size],
                                   hdr.Size + LTR030_FLASH_CONFIG_CRC_SIZE - hdr_size);
            if (err == LTR_OK) {
                err = f_flash_cfg_v2_check_crc(cfg_arr, hdr.Size);
            }

            if (err == LTR_OK) {
                memcpy(cfg, cfg_arr, sizeof(t_ltr030_flash_cfg_v2));
            }

            free(cfg_arr);
        }
    }

    return err;
}

static INT f_flash_cfg_v2_write(TLTR030 *hnd, DWORD addr, const TLTR030_CONFIG *cfg) {
    t_ltr030_flash_cfg_v2_hdr hdr;
    int present = 0;
    INT err = LTR030_FlashRead(hnd, addr, (BYTE *)&hdr, sizeof(hdr));
    if (err == LTR_OK) {
        DWORD size=0;
        t_ltr030_flash_cfg_v2 *cfg_wr = 0;
        if (f_flash_cfg_v2_check_hdr(&hdr) == LTR_OK) {
            DWORD alloc_size = hdr.Size;
            if (alloc_size < sizeof(t_ltr030_flash_cfg_v2))
                alloc_size = sizeof(t_ltr030_flash_cfg_v2);
            cfg_wr = malloc(alloc_size + LTR030_FLASH_CONFIG_CRC_SIZE);
            if (cfg_wr == NULL) {
                err = LTR_ERROR_MEMORY_ALLOC;
            } else {
                size = hdr.Size;
                err = LTR030_FlashRead(hnd, addr, (BYTE*)cfg_wr, size + LTR030_FLASH_CONFIG_CRC_SIZE);
            }

            if (err == LTR_OK) {
                if (f_flash_cfg_v2_check_crc((BYTE*)cfg_wr, hdr.Size) == LTR_OK) {
                    present = 1;
                }
            }
        }


        if (err == LTR_OK) {
            if (size == 0) {
                size = sizeof(t_ltr030_flash_cfg_v2);
                cfg_wr = malloc(size + LTR030_FLASH_CONFIG_CRC_SIZE);
                if (cfg_wr == NULL)
                    err = LTR_ERROR_MEMORY_ALLOC;
            }

        }


        if (err == LTR_OK) {
            WORD calc_crc;
            BYTE *cfg_arr = (BYTE *)cfg_wr;
            /* если конфигурация не была найдена, то заполняем поля сами */
            if (!present) {
                memset(cfg_arr, 0, sizeof(t_ltr030_flash_cfg_v2));
                cfg_wr->hdr.Sign = LTR030_FLASH_CONFIG_SIGN;
                cfg_wr->hdr.Size = sizeof(t_ltr030_flash_cfg_v2);
            } else if (size < sizeof(t_ltr030_flash_cfg_v2)) {
                /* если конфигурация была найдена, но ее размер меньше чем мы знаем,
                   то расширяем ее размер до известного, заполняя новые поля 0-ми */
                memset(&cfg_arr[size], 0, sizeof(t_ltr030_flash_cfg_v2) - size);
                cfg_wr->hdr.Size = sizeof(t_ltr030_flash_cfg_v2);
            }

            cfg_wr->device_mode = f_set_iface(cfg->Interface);

            if (cfg->Size >= 8) {
                cfg_wr->host_addr[0] = (cfg->IpAddr >> 24) & 0xFF;
                cfg_wr->host_addr[1] = (cfg->IpAddr >> 16) & 0xFF;
                cfg_wr->host_addr[2] = (cfg->IpAddr >> 8) & 0xFF;
                cfg_wr->host_addr[3] = cfg->IpAddr & 0xFF;
            }
            if (cfg->Size >= 12) {
                cfg_wr->gateway_addr[0] = (cfg->Gateway >> 24) & 0xFF;
                cfg_wr->gateway_addr[1] = (cfg->Gateway >> 16) & 0xFF;
                cfg_wr->gateway_addr[2] = (cfg->Gateway >> 8) & 0xFF;
                cfg_wr->gateway_addr[3] = cfg->Gateway & 0xFF;
            }
            if (cfg->Size >= 16) {
                cfg_wr->netmask[0] = (cfg->IpMask >> 24) & 0xFF;
                cfg_wr->netmask[1] = (cfg->IpMask >> 16) & 0xFF;
                cfg_wr->netmask[2] = (cfg->IpMask >> 8) & 0xFF;
                cfg_wr->netmask[3] = cfg->IpMask & 0xFF;
            }

            if (cfg->Size >= 22)
                memcpy(cfg_wr->mac_addr, cfg->UserMac, sizeof(cfg_wr->mac_addr));

            calc_crc = eval_crc16 (0xFFFFu, cfg_arr, size);
            cfg_arr[cfg_wr->hdr.Size] = calc_crc & 0xFF;
            cfg_arr[cfg_wr->hdr.Size + 1] = (calc_crc >> 8) & 0xFF;

            err = LTR030_FlashWrite(hnd, addr, cfg_arr, cfg_wr->hdr.Size+LTR030_FLASH_CONFIG_CRC_SIZE);
        }

        free(cfg_wr);
    }

    return err;
}



//-----------------------------------------------------------------------------
LTR030API_DllExport(INT) LTR030_Init(TLTR030 *hnd) {
    INT err =  hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err==LTR_OK) {
        memset(hnd, 0, sizeof(TLTR030));
        hnd->Size = sizeof(TLTR030);
        err = LTR_Init(&hnd->Channel);
    }

    return err;
}

//-----------------------------------------------------------------------------
LTR030API_DllExport(INT) LTR030_Open(TLTR030 *hnd, DWORD saddr, WORD sport,
                                     WORD iface, const CHAR *csn) {
    INT res = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (res==LTR_OK)
        res = LTR030_Close(hnd);
    if (res == LTR_OK) {
       res = LTR_OpenCrate(&hnd->Channel, saddr, sport, iface, csn);
        if (res==LTR_OK)
            res = LTR_GetCrateInfo(&hnd->Channel, &hnd->Info);
        if ((res==LTR_OK) && (hnd->Info.CrateType!=LTR_CRATE_TYPE_LTR030)
                && (hnd->Info.CrateType!=LTR_CRATE_TYPE_LTR031)) {
            res = LTR030_ERR_UNSUPORTED_CRATE_TYPE;
            LTR030_Close(hnd);
        }
    }
    return res;
}
//-----------------------------------------------------------------------------
LTR030API_DllExport(INT)  LTR030_Close(TLTR030 *hnd) {
    return hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_Close(&hnd->Channel);
}

LTR030API_DllExport(INT) LTR030_FlashWrite(TLTR030 *hnd, DWORD addr, const BYTE *data, DWORD size) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : data == NULL ? LTR_ERROR_PARAMETERS :
                                                                               LTR_OK;
    while ((err==LTR_OK) && size) {
        DWORD snd_size = size;
        if (snd_size > LTR030_USB_MAX_CONTROL_LEN)
            snd_size = LTR030_USB_MAX_CONTROL_LEN;

        err = LTR_CrateCustomCtlReq(&hnd->Channel, LTR030_USB_CMD_WRITE_DSP_FLASH,
                                   addr, data, snd_size, NULL, 0, NULL);
        if (err==LTR_OK) {
            addr+=snd_size;
            size-=snd_size;
            data+=snd_size;
        }
    }
    return err;
}

LTR030API_DllExport(INT) LTR030_FlashRead(TLTR030 *hnd, DWORD addr, BYTE *data, DWORD size) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR :
                               data == NULL ? LTR_ERROR_PARAMETERS : LTR_OK;
    while ((err==LTR_OK) && size) {
        DWORD rcv_size = size;
        if (rcv_size > LTR030_USB_MAX_CONTROL_LEN)
            rcv_size = LTR030_USB_MAX_CONTROL_LEN;

        err = LTR_CrateCustomCtlReq(&hnd->Channel, LTR030_USB_CMD_READ_DSP_FLASH,
                                   addr, NULL, 0, data, rcv_size, NULL);
        if (err==LTR_OK) {
            addr+=rcv_size;
            size-=rcv_size;
            data+=rcv_size;
        }
    }
    return err;
}

LTR030API_DllExport(INT) LTR030_SetInterfaceUsb(TLTR030 *hnd) {
    TLTR030_CONFIG cfg;
    INT err;
    cfg.Size = sizeof(cfg);
    err = LTR030_GetConfig(hnd, &cfg);
    if (err == LTR_OK) {
        cfg.Interface = LTR_CRATE_IFACE_USB;
        err = LTR030_SetConfig(hnd, &cfg);
    }
    return err;
}




LTR030API_DllExport(INT) LTR030_SetInterfaceTcp(TLTR030 *hnd, DWORD ip_addr,
                                                DWORD ip_mask, DWORD gate) {
    TLTR030_CONFIG cfg;
    INT err;
    cfg.Size = sizeof(cfg);
    err = LTR030_GetConfig(hnd, &cfg);
    if (err == LTR_OK) {
        cfg.Interface = LTR_CRATE_IFACE_TCPIP;
        cfg.IpAddr = ip_addr;
        cfg.IpMask = ip_mask;
        cfg.Gateway =  gate;
        err = LTR030_SetConfig(hnd, &cfg);
    }
    return err;
}


LTR030API_DllExport(INT) LTR030_GetConfig(TLTR030 *hnd, TLTR030_CONFIG *cfg) {
    INT err = (cfg==NULL) || (cfg->Size < 2) ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err==LTR_OK) {
        /* сперва пробуем прочитать конфигурацию во второй версии формата. она
           представлена в 2-х копиях. сперва читаем первую копию, если не обнаружили
           - вторую */
        t_ltr030_flash_cfg_v2 cfg_v2;
        INT cfg_err;

        memset(&cfg_v2, 0, sizeof(cfg_v2));
        cfg_err = f_flash_cfg_v2_read(hnd, LTR030_FLASH_ADDR_CONFIG2_MAIN, &cfg_v2);
        if (cfg_err != LTR_OK)
            cfg_err = f_flash_cfg_v2_read(hnd, LTR030_FLASH_ADDR_CONFIG2_SEC, &cfg_v2);

        if (cfg_err == LTR_OK) {
            WORD ret_size = 2;
            if (cfg->Size >= 4) {
                cfg->Interface = f_get_iface(cfg_v2.device_mode);
                ret_size = 4;
            }
            if (cfg->Size >= 8) {
                cfg->IpAddr = ((DWORD)cfg_v2.host_addr[0] << 24) | ((DWORD)cfg_v2.host_addr[1] << 16) |
                        ((DWORD)cfg_v2.host_addr[2] << 8) | cfg_v2.host_addr[3];
                ret_size = 8;
            }
            if (cfg->Size >= 12) {
                cfg->Gateway = ((DWORD)cfg_v2.gateway_addr[0] << 24) | ((DWORD)cfg_v2.gateway_addr[1] << 16) |
                        ((DWORD)cfg_v2.gateway_addr[2] << 8) | cfg_v2.gateway_addr[3];
                ret_size = 12;
            }
            if (cfg->Size >= 16) {
                cfg->IpMask = ((DWORD)cfg_v2.netmask[0] << 24) | ((DWORD)cfg_v2.netmask[1] << 16) |
                        ((DWORD)cfg_v2.netmask[2] << 8) | cfg_v2.netmask[3];
                ret_size = 16;
            }
            if (cfg->Size >= 22) {
                memcpy(cfg->UserMac, cfg_v2.mac_addr, sizeof(cfg->UserMac));
                ret_size = 22;
            }
            cfg->Size = ret_size;
        } else {
            BYTE raw_cfg[LTR030_CONFIG_SIZE];
            WORD ret_size = 2;
            err = LTR030_FlashRead(hnd, LTR030_FLASH_ADDR_CONFIG, raw_cfg, sizeof(raw_cfg));
            if (err==LTR_OK) {
                if (cfg->Size >= 4) {
                    WORD intf_code = raw_cfg[0] | ((WORD)raw_cfg[1] << 8);
                    cfg->Interface = f_get_iface(intf_code);
                    ret_size = 4;
                }
                if (cfg->Size >= 8) {
                    cfg->IpAddr = ((DWORD)raw_cfg[2] << 24) | ((DWORD)raw_cfg[3] << 16) |
                            ((DWORD)raw_cfg[4] << 8) | raw_cfg[5];
                    ret_size = 8;
                }
                if (cfg->Size >= 12) {
                    cfg->Gateway = ((DWORD)raw_cfg[6] << 24) | ((DWORD)raw_cfg[7] << 16) |
                            ((DWORD)raw_cfg[8] << 8) | raw_cfg[9];
                    ret_size = 12;
                }
                if (cfg->Size >= 16) {
                    cfg->IpMask = ((DWORD)raw_cfg[10] << 24) | ((DWORD)raw_cfg[11] << 16) |
                            ((DWORD)raw_cfg[12] << 8) | raw_cfg[13];
                    ret_size = 16;
                }
                cfg->Size = ret_size;
            }
        }
    }
    return err;
}

LTR030API_DllExport(INT) LTR030_GetFactoryMac(TLTR030 *hnd, BYTE *mac) {
    INT err = LTR_OK;
    /* сперва пытаемся прочитать информацию из области с неизменяемыми настройками,
       где есть серийный номер и заводской MAC-адрес */
    t_ltr030_fabric_info *finfo = malloc(sizeof(t_ltr030_fabric_info));
    if (finfo == NULL) {
        err = LTR_ERROR_MEMORY_ALLOC;
    } else {
        err = LTR030_FlashRead(hnd, LTR030_FLASH_ADDR_FABRIC_INFO, (BYTE*)finfo, sizeof(t_ltr030_fabric_info));
        if (err == LTR_OK) {
            WORD calc_crc = eval_crc16 (0xFFFFu, (unsigned char*) finfo, sizeof(t_ltr030_fabric_info)-2);
            if (calc_crc != finfo->crc) {
                err = LTR_ERROR_FLASH_INFO_CRC;
            } else {
                memcpy(mac, finfo->mac, sizeof(finfo->mac));
            }
        }
        free(finfo);
    }

    if (err != LTR_OK) {
        /* если там не обнаружили информацию, то читаем из старого места - за
           обычными настройками */
        err = LTR030_FlashRead(hnd, LTR030_FLASH_ADDR_MAC_OLD, mac, 6);
    }
    return err;
}



LTR030API_DllExport(INT) LTR030_SetConfig(TLTR030 *hnd, const TLTR030_CONFIG* cfg) {
    INT err = (cfg==NULL) || (cfg->Size < 4) ||
            ((cfg->Interface != LTR_CRATE_IFACE_TCPIP) && (cfg->Interface!=LTR_CRATE_IFACE_USB))
            ? LTR_ERROR_PARAMETERS : LTR_OK;

    if (err == LTR_OK)
        err = f_flash_cfg_v2_write(hnd, LTR030_FLASH_ADDR_CONFIG2_MAIN, cfg);

    if (err == LTR_OK)
        err = f_flash_cfg_v2_write(hnd, LTR030_FLASH_ADDR_CONFIG2_SEC, cfg);

    if (err == LTR_OK) {
        BYTE raw_cfg[LTR030_CONFIG_SIZE];
        WORD wr_size = 0;
        WORD intf = f_set_iface(cfg->Interface);


        raw_cfg[wr_size++] = intf & 0xFF;
        raw_cfg[wr_size++] = (intf >> 8) & 0xFF;

        if (cfg->Size >= 8) {
            raw_cfg[wr_size++] = (cfg->IpAddr >> 24) & 0xFF;
            raw_cfg[wr_size++] = (cfg->IpAddr >> 16) & 0xFF;
            raw_cfg[wr_size++] = (cfg->IpAddr >>  8) & 0xFF;
            raw_cfg[wr_size++] = cfg->IpAddr & 0xFF;
        }
        if (cfg->Size >= 12) {
            raw_cfg[wr_size++] = (cfg->Gateway >> 24) & 0xFF;
            raw_cfg[wr_size++] = (cfg->Gateway  >> 16) & 0xFF;
            raw_cfg[wr_size++] = (cfg->Gateway  >>  8) & 0xFF;
            raw_cfg[wr_size++] = cfg->Gateway  & 0xFF;
        }
        if (cfg->Size >= 16) {
            raw_cfg[wr_size++] = (cfg->IpMask >> 24) & 0xFF;
            raw_cfg[wr_size++] = (cfg->IpMask >> 16) & 0xFF;
            raw_cfg[wr_size++] = (cfg->IpMask >>  8) & 0xFF;
            raw_cfg[wr_size++] = cfg->IpMask & 0xFF;
        }
        err = LTR030_FlashWrite(hnd, LTR030_FLASH_ADDR_CONFIG, raw_cfg, wr_size);
    }
    return err;
}



#define CALL_CB_LOAD()  if (cb) cb(stage, proc_size, total_size, cb_data);


static INT f_write_from_block(TLTR030 *hnd, FILE* f, DWORD addr, DWORD max_size,
                              TLTR030_LOAD_PROGR_CB cb, void* cb_data) {
    INT err = LTR_OK;
    BYTE *block=NULL, *ver_block=NULL;
    DWORD total_size, proc_size=0;
    e_LTR030_LOAD_STAGES stage = LTR030_LOAD_STAGE_WRITE;

    block = malloc(LTR030_USB_MAX_CONTROL_LEN);
    ver_block = malloc(LTR030_USB_MAX_CONTROL_LEN);
    if ((block==NULL) || (ver_block==NULL)) {
        err = LTR_ERROR_MEMORY_ALLOC;
    } else {
        /* определяем полный разер файла */
        fseek(f, 0, SEEK_END);
        total_size = ftell(f);
        fseek(f, 0, SEEK_SET);

        if (total_size > max_size)
            err = LTR030_ERR_FIRM_SIZE;
    }

    if (err==LTR_OK) {
        CALL_CB_LOAD();

        while ((total_size-proc_size) && (err==LTR_OK)) {
            DWORD block_size = total_size-proc_size, rd_size;
            if (block_size>LTR030_USB_MAX_CONTROL_LEN)
                block_size = LTR030_USB_MAX_CONTROL_LEN;

            rd_size = (DWORD)fread(block, 1, block_size, f);
            if (rd_size!=block_size) {
                err = LTR_ERROR_FIRM_FILE_READ;
            } else {
                err = LTR030_FlashWrite(hnd, addr, block, block_size);
                if (err==LTR_OK) {
                    proc_size+=block_size;
                    addr+=block_size;
                    CALL_CB_LOAD();
                }
            }
        }

        if (err==LTR_OK) {
            stage = LTR030_LOAD_STAGE_VERIFY;
            proc_size = 0;
            addr-=total_size;
            fseek(f, 0, SEEK_SET);

            CALL_CB_LOAD();

            while ((total_size-proc_size) && (err==LTR_OK)) {
                DWORD block_size = total_size-proc_size, rd_size;
                if (block_size>LTR030_USB_MAX_CONTROL_LEN)
                    block_size = LTR030_USB_MAX_CONTROL_LEN;

                rd_size = (DWORD)fread(block, 1, block_size, f);
                if (rd_size!=block_size) {
                    err = LTR_ERROR_FIRM_FILE_READ;
                } else {
                    err = LTR030_FlashRead(hnd, addr, ver_block, block_size);
                    if (err==LTR_OK) {
                        unsigned i;
                        for (i=0; (i < block_size) && (err==LTR_OK); i++) {
                            if (block[i]!=ver_block[i])
                                err = LTR030_ERR_FIRM_VERIFY;
                        }

                    }
                    if (err==LTR_OK) {
                        proc_size+=block_size;
                        addr+=block_size;
                        CALL_CB_LOAD();
                    }
                }
            }
        }
    }

    free(block);
    free(ver_block);

    return err;
}



LTR030API_DllExport(INT) LTR030_LoadDspFirmware(TLTR030 *hnd, const char* filename,
                                                TLTR030_LOAD_PROGR_CB cb, void* cb_data) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : filename == NULL ?
                                   LTR_ERROR_PARAMETERS : LTR_OK;
    if (err==LTR_OK) {
        FILE *f = fopen(filename, "rb");

        if (f==NULL) {
            err = LTR_ERROR_FIRM_FILE_OPEN;
        } else {
            err = f_write_from_block(hnd, f, LTR030_FLASH_ADDR_DSP_FIRM,
                                    LTR030_FLASH_DSP_FIRM_MAX_SIZE,
                                     cb, cb_data);

            fclose(f);
        }
    }
    return err;
}


LTR030API_DllExport(INT) LTR030_LoadFpgaFirmware(TLTR030 *hnd, const char* filename,
                                                 TLTR030_LOAD_PROGR_CB cb, void* cb_data) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : filename == NULL ?
                                   LTR_ERROR_PARAMETERS : LTR_OK;
    if (err==LTR_OK) {
        FILE *f = fopen(filename, "rb");

        if (f==NULL) {
            err = LTR_ERROR_FIRM_FILE_OPEN;
        } else {
            BYTE *block = malloc(LTR030_USB_MAX_CONTROL_LEN);
            if (block==NULL) {
                err = LTR_ERROR_MEMORY_ALLOC;
            } else {
                BYTE info[LTR030_FPGA_INFO_SIZE];
                DWORD total_size, proc_size=0;
                DWORD chksum=0;

                fseek(f, 0, SEEK_END);
                total_size = ftell(f);
                fseek(f, 0, SEEK_SET);

                if (total_size > LTR030_FLASH_FPGA_FRIM_MAX_SIZE) {
                    err = LTR030_ERR_FIRM_SIZE;
                }

                while ((total_size-proc_size) && (err==LTR_OK)) {
                    DWORD block_size = total_size-proc_size, rd_size;
                    if (block_size>LTR030_USB_MAX_CONTROL_LEN)
                        block_size = LTR030_USB_MAX_CONTROL_LEN;

                    rd_size = (DWORD)fread(block, 1, block_size, f);
                    if (rd_size!=block_size) {
                        err = LTR_ERROR_FIRM_FILE_READ;
                    } else {
                        unsigned i;
                        for (i=0; i < block_size; i++) {
                            chksum = (chksum + block[i]) & 0xFFFFFFFFUL;
                        }

                        proc_size+=block_size;
                    }
                }

                if (err==LTR_OK) {
                    info[ 0 ] = total_size & 0xFF;
                    info[ 1 ] = (total_size >> 8) & 0xFF;
                    info[ 2 ] = (total_size >> 16) & 0xFF;
                    info[ 3 ] = (total_size >> 24) & 0xFF;
                    info[ 4 ] = chksum & 0xFF;
                    info[ 5 ] = (chksum >> 8) & 0xFF;
                    info[ 6 ] = (chksum >> 16) & 0xFF;
                    info[ 7 ] = (chksum >> 24) & 0xFF;
                    err = LTR030_FlashWrite(hnd, LTR030_FLASH_ADDR_FPGA_INFO,
                                        info, sizeof(info));
                }
                free(block);
            }

            if (err==LTR_OK) {
                err = f_write_from_block(hnd, f, LTR030_FLASH_ADDR_FPGA_FIRM,
                                         LTR030_FLASH_FPGA_FRIM_MAX_SIZE, cb, cb_data);
            }

            fclose(f);
        }
    }
    return err;
}

LTR030API_DllExport(INT) LTR030_CrateReset(TLTR030 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK)
        err = LTR_CrateCustomCtlReq(&hnd->Channel, LTR030_USB_CMD_RESET, 0, NULL, 0, NULL, 0, NULL);
    return err;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
LTR030API_DllExport(LPCSTR)  LTR030_GetErrorString(INT error) {
    size_t i;
    for (i = 0; i < sizeof(f_err_str_tbl) / sizeof(f_err_str_tbl[0]); i++) {
        if (f_err_str_tbl[i].code == error)
            return f_err_str_tbl[i].message;
    }
    return LTR_GetErrorString(error);
}
//-----------------------------------------------------------------------------
#ifdef _WIN32
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) {
    return 1;
}
#endif
