/*********************************************************************************************************//**
 * @file    IAP_HID/IAP_HID_III/Src_IAP/iap_handler.c
 * @version $Rev:: 763          $
 * @date    $Date:: 2025-09-03 #$
 * @brief   This file contains IAP handler.
 *************************************************************************************************************
 * @attention
 *
 * Firmware Disclaimer Information
 *
 * 1. The customer hereby acknowledges and agrees that the program technical documentation, including the
 *    code, which is supplied by Holtek Semiconductor Inc., (hereinafter referred to as "HOLTEK") is the
 *    proprietary and confidential intellectual property of HOLTEK, and is protected by copyright law and
 *    other intellectual property laws.
 *
 * 2. The customer hereby acknowledges and agrees that the program technical documentation, including the
 *    code, is confidential information belonging to HOLTEK, and must not be disclosed to any third parties
 *    other than HOLTEK and the customer.
 *
 * 3. The program technical documentation, including the code, is provided "as is" and for customer reference
 *    only. After delivery by HOLTEK, the customer shall use the program technical documentation, including
 *    the code, at their own risk. HOLTEK disclaims any expressed, implied or statutory warranties, including
 *    the warranties of merchantability, satisfactory quality and fitness for a particular purpose.
 *
 * <h2><center>Copyright (C) Holtek Semiconductor Inc. All rights reserved</center></h2>
 ************************************************************************************************************/

/* Includes ------------------------------------------------------------------------------------------------*/
#include "ht32.h"
#include "ht32_board.h"
#include "ht32_board_config.h"

#include "iap_handler.h"
#include "iap_config.h"
#include "iap_crc16.h"
#include "mbedtls/arc4.h"
#include "USBBufCheck.h"

#include "common\ht32_misc.h"

/** @addtogroup HT32_Series_Peripheral_Examples HT32 Peripheral Examples
  * @{
  */

/** @addtogroup IAP_HID IAP HID
  * @{
  */

/** @addtogroup IAP_HID_III IAP HID III (with Encrypt and Command Key)
  * @{
  */

/** @addtogroup IAP_HID_III_IAP IAP Example
  * @{
  */


/* Private types -------------------------------------------------------------------------------------------*/
typedef u32 (*pFunction)(u32, u32, u32);

/* Exported typedef ----------------------------------------------------------------------------------------*/
typedef __PACKED_H struct
{
  u16 uVersionLength;
  u16 uHeader;
  u8 uVersion[12];
  u32 uFWID;
  u32 uChecksum;
} __PACKED_F InfoPage_TypeDef;

/* Private constants ---------------------------------------------------------------------------------------*/
#define __DBG_IAPPrintf(...)
#define __DBG_IAP_DumpData(...)

#if (IAP_DEBUG_MODE == 1)
  #undef __DBG_IAPPrintf
  #undef __DBG_IAP_DumpData
  #define __DBG_IAPPrintf printf
  static void __DBG_IAP_DumpData(uc8 *memory, u32 len);
  #warning "================================ IAP Debug Mode Warning ================================"
  #warning " 1. IAP debug mode has been enable which degrade the performance.                       "
  #warning " 2. Note that print debug message too much may cause the IAP command not synchronized.  "
  #warning " 3. Remember to modify Host/Device IAP_CODE_PAGE define according to the IAP Size       "
  #warning "    after enable IAP debug mode.                                                        "
  #warning " 4. After all debug operation is finished, please remember to turn off IAP debug mode.  "
  #warning "========================================================================================"
#endif


#define MAX_CMD_LEN             (64)
#define MAX_TOKENS              (3)
#define ISP_CMD_COUNT           (6)
#define CMD_COUNT               (ISP_CMD_COUNT + USER_CMD_COUNT)
#define INF_COUNT               (5)
#define CMD_SUCCESS             ('O')
#define CMD_FAILED              ('F')
#define USER_CMD_START          (0x50)
#define CMD_PAYLOAD_ADDR        (12)
#define CMD_PAYLOAD_LEN         (52)

#define CMD_PAGE_ERASE          (1)
#define CMD_MASS_ERASE          (0)

#define CMD_VERIFY              (0)
#define CMD_PROGRAM             (1)
#define CMD_READ                (2)
#define CMD_BLANK               (3)

#define LOADER_MODE             (0x1)
#define LOADER_FLASH_START      (IAP_CODE_SIZE)
#define LOADER_CHIP_NAME        (0)
#define LOADER_PAGE_SIZE        (LIBCFG_FLASH_PAGESIZE)
#define LOADER_FLASH_SIZE       (LIBCFG_FLASH_SIZE - IAP_CODE_SIZE)
#define LOADER_FLASH_NUM        (LOADER_FLASH_SIZE/LIBCFG_FLASH_PAGESIZE)
#define LOADER_PPBIT_NUM        (0) // Not applicable for IAP
#define LOADER_FULL_CHIP_NAME   ((DEBUGMCU->pid <= 0x50092093 && DEBUGMCU->pid >= 0x50092081) ? 0x49031 : \
                                 (DEBUGMCU->pid <= 0x50092112 && DEBUGMCU->pid >= 0x50092100) ? 0x49041 : \
                                 (DEBUGMCU->pid <= 0x700A21CD && DEBUGMCU->pid >= 0x700A21C1) ? 0x49153 : \
                                 (DEBUGMCU->pid <= 0x700A324C && DEBUGMCU->pid >= 0x700A3240) ? 0x49163 : \
                                 (DEBUGMCU->pid <= 0x70050247 && DEBUGMCU->pid >= 0x70050244) ? 0x49365 : \
                                 (DEBUGMCU->pid <= 0x70050343 && DEBUGMCU->pid >= 0x70050340) ? 0x49395 :0)

#define LOADER_INFO0            (u32)((LOADER_MODE      << 28) | (LOADER_VERSION     << 16) | LOADER_CHIP_NAME)
#define LOADER_INFO1            (u32)((LOADER_PAGE_SIZE << 16) | (LOADER_FLASH_START << 0))
#define LOADER_INFO2            (u32)((LOADER_FLASH_NUM << 16) | (LOADER_PPBIT_NUM   << 0))
#define LOADER_INFO3            (u32)(IAP_VERFLASH_START - FLASH_BASE)
#define LOADER_INFO4            (u32)(LOADER_FULL_CHIP_NAME)

#define FLASH_CMD_PROGRAM       ((u32)0x00000004)
#define FLASH_SEND_MAIN         ((u32)0x00000014)

#define IAP_PAGE_ERASE          (0x8)
#define IAP_MASS_ERASE          (0xA)

#define IAP_APFLASH_END         (FLASH_BASE + IAP_APFLASH_SIZE - 1)
#define IAP_APSRAM_END          (SRAM_BASE + IAP_APSRAM_SIZE)

#define INFO_OFFSET             (IAP_VER_ADDR)
#define INFO_OFFSET_CHECKSUM    (0x24)

/* Private function prototypes -----------------------------------------------------------------------------*/
#if (ARC4_DECRYPTION_EN == 1)
static u32 _IAP_decrypt(u8 *ibuf, u8 *obuf, u8 len);
#endif
#if (RESTRAINT_PROGRAMMING_FLOW_EN == 1)
static u8 _IAP_isValidationErase(void);
#endif
static u32 _IAP_Erase(u32 type, u32 saddr, u32 eaddr);
static u32 _IAP_Flash(u32 type, u32 saddr, u32 eaddr);
static u32 _IAP_CRC(u32 crc, u32 saddr, u32 length);
static u32 _IAP_Info(void);
static void _IAP_Exit(void);

#if (EXT_FLASH_ENABLE == 1)
#if 0
/* !!! NOTICE !!!
   Notice that External Flash is not yet supported.
*/
static u32 _IAP_EXT_FlashBlank(u32 saddr, u32 length);
static u32 _IAP_EXT_FlashVerify(u32 saddr, u32 length, u8 *pbuffer);
static u32 _IAP_EXT_Flash(u32 type, u32 saddr, u32 eaddr);
static u32 _IAP_EXT_Erase(u32 type, u32 saddr, u32 eaddr);
#endif
#endif

#if (USER_CMD_DEMO == 1)
static u32 _User_Cmd50Example(u32 uPar0, u32 uPar1, u32 uPar2);
static u32 _User_Cmd51Example(u32 uPar0, u32 uPar1, u32 uPar2);
#endif

static void _ImageInfo_ActiveImage(u32 uCurrentAddress);
static s32 _IAP_CalcAPImageCRC(s32 sLength);

/* Global variables ----------------------------------------------------------------------------------------*/
u8 gisDigestCheck = FALSE;
u8 gisVersionErased = FALSE;

u16 guDigestResult = 0;
u32 gAPLength = 0;

u32 gEraseBitmap[32];

u32 guIsFirstProgramCmd = FALSE;
u32 guFirstProgramData[13];
u32 guIsFirstVerifyCmd = FALSE;

/* Private variables ---------------------------------------------------------------------------------------*/
static u32 gu32Infotable[INF_COUNT];

static u8 *gpCmdBuffer;
static u8 gResponseLength;

#if (USER_CMD_DEMO == 1)
__ALIGN4 static u8 uUserData[52];
#endif

static const pFunction pFComHandlerTable[CMD_COUNT] =
{
  (pFunction)_IAP_Erase,
  (pFunction)_IAP_Flash,
  (pFunction)_IAP_CRC,
  (pFunction)_IAP_Info,
  (pFunction)IAP_Reset,
  (pFunction)_IAP_Exit,
  #if (USER_CMD_DEMO == 1)
  (pFunction)_User_Cmd50Example,
  (pFunction)_User_Cmd51Example
  #endif
};

#if (ARC4_DECRYPTION_EN == 1) || (DIGEST_CHECK_EN == 1)
/* !!! NOTICE !!!
   You must change the key! DO NOT use the default one.
   Take your own risk to use the security architecture of this example.
*/
static const unsigned char Encrypt_Key[ARC4_KEY_LEN] = {
  0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
};
#endif

#if (COMMAND_KEY_EN == 1)
/* !!! NOTICE !!!
   You must change the key! DO NOT use the default one.
   Take your own risk to use the security architecture of this example.
*/
static const unsigned char Command_Key[COMMAND_KEY_LEN] = {
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
};
#endif

/* Global functions ----------------------------------------------------------------------------------------*/
/*********************************************************************************************************//**
  * @brief  IAP mode initialization.
  * @retval None
  ***********************************************************************************************************/
void IAP_Init(void)
{
  gu32Infotable[0] = LOADER_INFO0;
  gu32Infotable[1] = LOADER_INFO1;
  gu32Infotable[2] = LOADER_INFO2;
  gu32Infotable[3] = LOADER_INFO3;
  gu32Infotable[4] = LOADER_INFO4;
}

/*********************************************************************************************************//**
  * @brief  IAP command handler.
  * @retval None
  ***********************************************************************************************************/
u32 IAP_CMDHandler(u8 *puCmdBuffer)
{
  #if (IAP_TIMEOUT_EN == 1)
  extern s32 gIAPTimeout;
  #endif
  #if (IAP_DOUBLE_RESET_EN == 1)
  extern s32 gDoubleTimeout;
  #endif
  u32 u32Parameter[MAX_TOKENS];
  u32 u32CommandExecuteResult;
  u16 crc;
  u16 crcValue;

  #if 0
  __DBG_IAP_DumpData(puCmdBuffer ,64);
  #endif

  gpCmdBuffer = puCmdBuffer;
  gResponseLength = 0;

  /*--------------------------------------------------------------------------------------------------------*/
  /* Check CRC value of command packet                                                                      */
  /*--------------------------------------------------------------------------------------------------------*/
  crc = puCmdBuffer[2] | ((u16)puCmdBuffer[3] << 8);
  puCmdBuffer[2] = puCmdBuffer[3] = 0;
  crcValue = CRC16(0, (u8 *)(&puCmdBuffer[0]), 64);

  #if (COMMAND_KEY_EN == 1)
  crcValue = CRC16(crcValue, (u8 *)Command_Key, sizeof(Command_Key));
  #endif

  if (puCmdBuffer[0] >= USER_CMD_START)
  {
    puCmdBuffer[0] = puCmdBuffer[0] - USER_CMD_START + ISP_CMD_COUNT;
  }

  /*--------------------------------------------------------------------------------------------------------*/
  /* Check command is valid and CRC is correct                                                              */
  /*--------------------------------------------------------------------------------------------------------*/
  if (puCmdBuffer[0] >= CMD_COUNT)
  {
    /*------------------------------------------------------------------------------------------------------*/
    /* Command invalid. Return 'F' and clear command buffer                                                 */
    /*------------------------------------------------------------------------------------------------------*/
    u32CommandExecuteResult = CMD_FAILED;
    __DBG_IAPPrintf("CMD ERR!\r\n");
  }
  else if (crc != crcValue)
  {
    /*------------------------------------------------------------------------------------------------------*/
    /* Command key or CRC error.                                                                            */
    /*------------------------------------------------------------------------------------------------------*/
    u32CommandExecuteResult = CMD_FAILED;
    __DBG_IAPPrintf("CRC ERR!\r\n");
  }
  else
  {
    /*------------------------------------------------------------------------------------------------------*/
    /* Command is OK, stop gIAPTimeout count to prevent timeout reset                                       */
    /*------------------------------------------------------------------------------------------------------*/
    #if (IAP_TIMEOUT_EN == 1)
    gIAPTimeout = IAP_TIMEOUT_OFF;
    #endif
    #if (IAP_DOUBLE_RESET_EN == 1)
    gDoubleTimeout = IAP_TIMEOUT_OFF;
    HARDWAVE_RESET = 0;
    #endif

    /*------------------------------------------------------------------------------------------------------*/
    /* Prepare parameter and execution command                                                              */
    /*------------------------------------------------------------------------------------------------------*/
    u32Parameter[0] = puCmdBuffer[1];
    u32Parameter[1] = *((u32 *)(&(puCmdBuffer[4])));
    u32Parameter[2] = *((u32 *)(&(puCmdBuffer[8])));

    u32CommandExecuteResult = (*pFComHandlerTable[puCmdBuffer[0]])(u32Parameter[0],
                                                                   u32Parameter[1],
                                                                   u32Parameter[2]);
  }

  puCmdBuffer[gResponseLength] = u32CommandExecuteResult;
  gResponseLength++;

  __DBG_IAPPrintf("RESP len=%d,", gResponseLength);
  __DBG_IAP_DumpData(puCmdBuffer, gResponseLength);

  return gResponseLength;
}

/*********************************************************************************************************//**
  * @brief  Reset Command.
  * @param  uMode: Mode after reset
  * @retval CMD_SUCCESS or CMD_FAILED
  ***********************************************************************************************************/
u32 IAP_Reset(u32 uMode)
{
  __DBG_IAPPrintf("RST\r\n");

  _HT32_USBBufCheck_DeInit();

  if (uMode == 0)
  {
    if ((IAP_isAPValid() == FALSE) || (IAP_isVersionValid() == FALSE) || (IAP_isChecksumValid() == FALSE))
    {
      /* AP image is invalid. Jump to AP is not allow                                                       */
      __DBG_IAPPrintf("AP incorrect\n\r");
      return CMD_FAILED;
    }
    BOOT_MODE = BOOT_MODE_AP;
  }
  else
  {
    BOOT_MODE = BOOT_MODE_IAP;
  }

  IAPUser_Reset();

  return CMD_SUCCESS;
}

/*********************************************************************************************************//**
  * @brief  Check AP is valid or not.
  * @retval FALSE or TRUE
  ***********************************************************************************************************/
u32 IAP_isAPValid(void)
{
  unsigned int SP, PC;

  /* Check Stack Point in range                                                                             */
  SP = rw(IAP_APFLASH_START);
  if (SP < IAP_APSRAM_START || SP > IAP_APSRAM_END)
  {
    __DBG_IAPPrintf("AP SP Err[%08X]\n\r", SP);
    return FALSE;
  }

  /* Check PC in range                                                                                      */
  PC = rw(IAP_APFLASH_START + 0x4);
  if (PC < IAP_APFLASH_START || PC > IAP_APFLASH_END)
  {
    __DBG_IAPPrintf("AP PC Err[%08X]\n\r", PC);
    return FALSE;
  }

  /* AP CRC checking                                                                                        */
  #if (IAP_CRC_CHECK_EN == 1)
  {
    unsigned int uCRC;
    signed int sLength;
    unsigned int uCRCResult;

    uCRC = rw(IAP_APFLASH_START + IAP_CRC_ADDR);
    sLength = rw(IAP_APFLASH_START + IAP_APEND_ADDR);
    #if (IAP_CRC_SAVETYPE == 1) // 1 for Save End Addrss, 0 for Save Length
    sLength = sLength + 1 - IAP_APFLASH_START;
    #endif
    if (sLength < 0)
    {
      return FALSE;
    }
    if ((uCRC & 0xFFFF0000) == IAP_CRC_KEY)
    {
      uCRCResult = _IAP_CalcAPImageCRC(sLength);
      if ((uCRC & 0x0000FFFF) != uCRCResult)
      {
        __DBG_IAPPrintf("AP CRC Err[%08X][%08X][%d]\n\r", uCRC, uCRCResult, sLength);
        return FALSE;
      }
    }
  }
  #endif

  return TRUE;
}

/*********************************************************************************************************//**
  * @brief  Check saddr is in the version range or not.
  * @param  saddr: address
  * @retval FALSE or TRUE
  ***********************************************************************************************************/
u32 IAP_isVersionRegion(u32 saddr)
{
  if ((saddr >= IAP_VERFLASH_START) && (saddr < (IAP_VERFLASH_START + IAP_VERSION_SIZE)))
    return TRUE;
  else
    return FALSE;
}

/*********************************************************************************************************//**
  * @brief  Check version is valid or not.
  * @retval FALSE or TRUE
  ***********************************************************************************************************/
u32 IAP_isVersionValid(void)
{
  unsigned int i, *pVer;
  InfoPage_TypeDef *pInfoPage = (InfoPage_TypeDef *)(IAP_VERFLASH_START);
  if (pInfoPage->uHeader == 0xFFFF)
  {
    return FALSE;
  }

  if (pInfoPage->uVersionLength > 12)
  {
    return FALSE;
  }
  for (i = 0; i < pInfoPage->uVersionLength; i += 4)
  {
    pVer = (unsigned int *)(&pInfoPage->uVersion[i]);
    if (*pVer == 0xFFFFFFFF)
    {
      __DBG_IAPPrintf("Version Err[%08X]\n\r", *pVer);
      return FALSE;
    }
  }

  return TRUE;
}

/*********************************************************************************************************//**
  * @brief  Check if the checksum is valid or not.
  * @retval FALSE or TRUE
  ***********************************************************************************************************/
u32 IAP_isChecksumValid(void)
{
  u32 *ptr = (u32 *)IAP_VERFLASH_START;
  InfoPage_TypeDef *pInfoPage = (InfoPage_TypeDef *)(IAP_VERFLASH_START);
  u32 uChecksum = 0;
  u32 i;

  for (i = 0; i < 5; i++)
  {
    uChecksum += *(ptr + i);
  }

  return (uChecksum == pInfoPage->uChecksum) ? TRUE : FALSE;
}

/*********************************************************************************************************//**
  * @brief  After checking that the AP is valid, jump to the user AP.
  * @param  address: Start address of user application
  * @retval None
  ***********************************************************************************************************/
void IAP_JumpToAP(u32 address)
{
  if ((IAP_isAPValid() == TRUE) && (IAP_isVersionValid() == TRUE) && (IAP_isChecksumValid() == TRUE))
  {
    /*------------------------------------------------------------------------------------------------------*/
    /* Start user application when                                                                          */
    /*   1. SP and PC of user's application is in range and                                                 */
    /*   2. This version is valid                                                                           */
    /*   3. This checksum is valid                                                                          */
    /*------------------------------------------------------------------------------------------------------*/
    IAP_GoCMD(address);
  }
}

#if defined (__CC_ARM)
/*********************************************************************************************************//**
  * @brief  Jump to user application by change PC.
  * @param  address: Start address of user application
  * @retval None
  ***********************************************************************************************************/
__asm void IAP_GoCMD(u32 address)
{
  LDR R1, [R0]
  MOV SP, R1
  LDR R1, [R0, #4]
  BX R1
}
#elif defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) || (__GNUC__)
__attribute__((noinline)) void IAP_GoCMD(u32 address)
{
  __ASM volatile ("LDR R1, [%0]" : : "r" (address) : );
  __ASM volatile ("MOV SP, R1");
  __ASM volatile ("LDR R1, [%0, #4]" : : "r" (address) : );
  __ASM volatile ("BX R1");
}
#elif defined (__ICCARM__)
void IAP_GoCMD(u32 address)
{
  __asm("LDR R1, [R0]");
  __asm("MOV SP, R1");
  __asm("LDR R1, [R0, #4]");
  __asm("BX R1");
}
#endif

/* Private functions ---------------------------------------------------------------------------------------*/
#if (ARC4_DECRYPTION_EN == 1)
/*********************************************************************************************************//**
  * @brief  ARC4 decryption.
  * @retval None
  ***********************************************************************************************************/
static u32 _IAP_decrypt(u8 *ibuf, u8 *obuf, u8 len)
{
  mbedtls_arc4_context ctx;

  mbedtls_arc4_init(&ctx);
  mbedtls_arc4_setup(&ctx, Encrypt_Key, ARC4_KEY_LEN);
  mbedtls_arc4_crypt(&ctx, len, ibuf, obuf);
  mbedtls_arc4_free(&ctx);

  return 0;
}
#endif

#if (RESTRAINT_PROGRAMMING_FLOW_EN == 1)
/*********************************************************************************************************//**
  * @brief Check erase range
  * @retval TRUE or FALSE
  ***********************************************************************************************************/
static u8 _IAP_isValidationErase(void)
{
  u32 idx;
  u32 shift;
  u32 i;

  for (i = 0; i < MINIMUM_ERASE_SIZE; i++)
  {
    idx = i / 32;
    shift = i % 32;
    if (((gEraseBitmap[idx] >> shift) & 0x1) == 0)
    {
      return FALSE;
    }
  }

  return TRUE;
}
#endif

/*********************************************************************************************************//**
  * @brief  Mass/Page Erase.
  * @param  type: Erase type
  *         @arg IAP_MASS_ERASE: Mass Erase (Not support in IAP mode)
  *         @arg IAP_PAGE_ERASE: Page Erase
  * @param  saddr: Start address
  * @param  eaddr: End address
  * @retval CMD_SUCCESS or CMD_FAILED
  ***********************************************************************************************************/
static u32 _IAP_Erase(u32 type, u32 saddr, u32 eaddr)
{
  u32 i;

  saddr += FLASH_BASE;
  eaddr += FLASH_BASE;

  __DBG_IAPPrintf("E %x %x\r\n", (unsigned int)saddr, (unsigned int)eaddr);

  guIsFirstProgramCmd = FALSE;
  guIsFirstVerifyCmd = FALSE;

  #if (EXT_FLASH_ENABLE == 1)
  #if 0
  /* !!! NOTICE !!!
     Notice that External Flash is not yet supported.
  */
  if (saddr >= EXT_FALSH_ADDRESS)
  {
    return (_IAP_EXT_Erase(type, saddr, eaddr));
  }
  #endif
  #endif

  if (type == IAP_MASS_ERASE || saddr < IAP_CODE_SIZE)
  {
    return CMD_FAILED;
  }

  #if (RESTRAINT_PROGRAMMING_FLOW_EN == 1)
  if (saddr == IAP_APFLASH_START)
  {
    for (i = 0; i < 32; i++)
    {
      gEraseBitmap[i] = 0;
    }
  }
  #endif

  #if (DIGEST_CHECK_EN == 1)
  if (saddr == IAP_VERFLASH_START)
  {
    gisVersionErased = TRUE;
  }
  else
  {
    if (gisVersionErased == FALSE)
    {
      return CMD_FAILED;
    }
  }
  #endif

  flash_unlock();
  for (i = saddr; i <= eaddr; i += LIBCFG_FLASH_PAGESIZE)
  {
    flash_sector_erase(i);

    #if (RESTRAINT_PROGRAMMING_FLOW_EN == 1)
    if (saddr >= IAP_APFLASH_START)
    {
      u32 idx = ((i - IAP_APFLASH_START) / 1024) / 32;
      u32 shift = ((i - IAP_APFLASH_START) / 1024) % 32;
      gEraseBitmap[idx] |= (1 << shift);
      gEraseBitmap[idx] |= (1 << (shift + 1));
    }
    #endif

    #if 0
    wdt_counter_reload();
    #endif
  }

  #if (DIGEST_CHECK_EN == 1)
  guDigestResult = 0;
  gAPLength = 0;
  gisDigestCheck = FALSE;
  #endif

  return CMD_SUCCESS;
}

/*********************************************************************************************************//**
  * @brief  Download image for program or verify.
  * @param  type: Program or verify
  *         @arg CMD_PROGRAM: Program mode
  *         @arg CMD_VERIFY: Verify mode
  *         @arg CMD_BLANK: Blank check mode
  *         @arg CMD_READ: Read mode
  * @param  saddr: Start address
  * @param  eaddr: End address
  * @retval CMD_SUCCESS or CMD_FAILED
  ***********************************************************************************************************/
static u32 _IAP_Flash(u32 type, u32 saddr, u32 eaddr)
{
  u32 *buffer = (u32 *)(&gpCmdBuffer[CMD_PAYLOAD_ADDR]);
  u32 data;

  saddr += FLASH_BASE;
  eaddr += FLASH_BASE;

  #if (EXT_FLASH_ENABLE == 1)
  #if 0
  /* !!! NOTICE !!!
     Notice that External Flash is not yet supported.
  */
  if (saddr >= EXT_FALSH_ADDRESS)
  {
    return (_IAP_EXT_Flash(type, saddr, eaddr));
  }
  #endif
  #endif

  /*--------------------------------------------------------------------------------------------------------*/
  /* Check command state and do ARC4 Decryption                                                             */
  /*--------------------------------------------------------------------------------------------------------*/
  if (type == CMD_PROGRAM || type == CMD_VERIFY)
  {
    if (IAP_isVersionRegion(saddr) == TRUE)
    {
      #if (DIGEST_CHECK_EN == 1)
      if (gisDigestCheck == FALSE)
      {
        return CMD_FAILED;
      }
      #endif
    }
    else
    {
      #if (DIGEST_CHECK_EN == 1)
      /* Prevent user updating firmware without clearing VERSION                                            */
      if (type == CMD_PROGRAM && gisVersionErased == FALSE)
      {
        return CMD_FAILED;
      }
      #endif

      #if (ARC4_DECRYPTION_EN == 1)
      _IAP_decrypt((u8*)buffer, (u8*)buffer, (eaddr - saddr + 1));
      #endif
    }
  }

  /*--------------------------------------------------------------------------------------------------------*/
  /* Program                                                                                                */
  /*--------------------------------------------------------------------------------------------------------*/
  if (type == CMD_PROGRAM)
  {
    __DBG_IAPPrintf("P\r\n");

    #if (RESTRAINT_PROGRAMMING_FLOW_EN == 1)
    if (_IAP_isValidationErase() == FALSE)
    {
      __DBG_IAPPrintf("RER\r\n");
      return CMD_FAILED;
    }
    #endif

    #if (DIGEST_CHECK_EN == 1)
    if (IAP_isVersionRegion(saddr) == FALSE)
    {
      guDigestResult = CRC16(guDigestResult, (u8 *)buffer, (eaddr - saddr + 1));
      gAPLength += (eaddr - saddr + 1);
    }
    else
    {
      gisVersionErased = FALSE;
    }
    #endif

    if (guIsFirstProgramCmd == FALSE)
    {
      u32 i;
      guIsFirstProgramCmd = TRUE;
      for (i = 0; i < 13; i++)
      {
        guFirstProgramData[i] = *buffer;
        saddr += 4;
        buffer++;
      }
    }
    else
    {
      if (saddr == IAP_VERFLASH_START)
      {
        /* Programming Version flow                                                                         */
        u32 i;
        u32 uStardAddr = IAP_APFLASH_START ;
        u32 uCRCResult;
        s32 sLength = rw(IAP_APFLASH_START + IAP_APEND_ADDR);
        #if (IAP_CRC_SAVETYPE == 1) // 1 for Save End Addrss, 0 for Save Length
        sLength = sLength + 1 - IAP_APFLASH_START;
        #endif
        *buffer |= 0x4F460000;
        for (i = 0; i < 13; i++)
        {
          if (i >= 4 && i <= 7) // Set Version
          {
             guFirstProgramData[i] = *buffer++;
          }
          else if(i == 9) //Skip Checksum
          {
            guFirstProgramData[i] = 0xFFFFFFFF;
          }
          else if(i == 10) ///Skip IAP_CRC_ADDR
          {
            guFirstProgramData[i] = 0xFFFFFFFF;
          }
          flash_word_program(uStardAddr, guFirstProgramData[i]);
          uStardAddr += 4;
        }

        /* This is the final and critical step of the AP programming process.                               */
        /* Note: Finalize AP programming by calculating and writing the Image Info checksum, and compute    */
        /*       and storethe AP image CRC at AP_CRC_ADDR.                                                  */
        _ImageInfo_ActiveImage(FLASH_BASE + IAP_CODE_SIZE); // Calc Checksum and Write checksum
        uCRCResult = IAP_CRC_KEY | _IAP_CalcAPImageCRC(sLength);
        if(uCRCResult != 0xFFFFFFFF)
        {
          flash_word_program(IAP_APFLASH_START + IAP_CRC_ADDR, uCRCResult); // Write IAP_CRC_ADDR
        }
      }
      else
      {
        /* Programming AP flow                                                                              */
        while (saddr <= eaddr)
        {
          #if 1
          // Do auto-blank check before program
          if (rw(saddr) != 0xFFFFFFFF)
          {
            return CMD_FAILED;
          }
          #endif

          flash_word_program(saddr, *buffer);

          #if 1
          // Do auto-verify after program
          if (rw(saddr) != *buffer)
          {
            return CMD_FAILED;
          }
          #endif

          saddr += 4;
          buffer++;
        }
      }
    }
    return CMD_SUCCESS;
  }

  /*--------------------------------------------------------------------------------------------------------*/
  /* Read                                                                                                   */
  /*--------------------------------------------------------------------------------------------------------*/
  if (type == CMD_READ)
  {
    u32 uTxIndex = 0;
    u32 uTxData[16];

    __DBG_IAPPrintf("R\r\n");

    while (saddr <= eaddr)
    {
      data = 0;
      if (saddr == IAP_VERFLASH_START)
      {
        data = rw(IAP_VERFLASH_START) & 0x0000FFFF;
      }
      else if (saddr == (IAP_VERFLASH_START + 4))
      {
        data = rw(IAP_VERFLASH_START + 4);
      }
      else if (saddr == (IAP_VERFLASH_START + 8))
      {
        data = rw(IAP_VERFLASH_START + 8);
      }
      else if (saddr == (IAP_VERFLASH_START + 12))
      {
        data = rw(IAP_VERFLASH_START + 12);
      }

      uTxData[uTxIndex] = data;
      uTxIndex++;

      if ((uTxIndex == 16) || ((saddr + 4) > eaddr))
      {
        uTxIndex = 0;

        IAPUser_SendData((u8 *)uTxData, 64);
        __DBG_IAP_DumpData((u8 *)uTxData ,64);
      }

      saddr += 4;
    }
    return CMD_SUCCESS;
  }

  if ((guIsFirstVerifyCmd == FALSE) && (type == CMD_VERIFY))
  {
    guIsFirstVerifyCmd = TRUE;
    return CMD_SUCCESS;
  }

  /*--------------------------------------------------------------------------------------------------------*/
  /* Verify                                                                                                 */
  /*--------------------------------------------------------------------------------------------------------*/
  if (type == CMD_VERIFY)
  {
    __DBG_IAPPrintf("V\r\n");
    while (saddr <= eaddr)
    {
      data = rw(saddr);
      if (saddr == IAP_VERFLASH_START)
      {
        data &= 0x0000FFFF;
      }
      if (data != *buffer)
      {
        return CMD_FAILED;
      }
      saddr += 4;
      buffer++;
    }
    return CMD_SUCCESS;
  }

  /*--------------------------------------------------------------------------------------------------------*/
  /* Blank                                                                                                  */
  /*--------------------------------------------------------------------------------------------------------*/
  if (type == CMD_BLANK)
  {
    __DBG_IAPPrintf("B\r\n");
    while (saddr <= eaddr)
    {
      data = rw(saddr);
      if (data != 0xFFFFFFFF)
      {
        __DBG_IAPPrintf("%x\r\n", (unsigned int)saddr);
        return CMD_FAILED;
      }
      saddr += 4;
    }
    return CMD_SUCCESS;
  }

  return CMD_SUCCESS;
}

#if (EXT_FLASH_ENABLE == 1)
#if 0
/* !!! NOTICE !!!
   Notice that External Flash is not yet supported.
*/
/*********************************************************************************************************//**
  * @brief  Mass/Page Erase EXT Flash.
  * @param  type: Erase type
  *         @arg IAP_MASS_ERASE: Mass Erase (Not support in IAP mode)
  *         @arg IAP_PAGE_ERASE: Page Erase
  * @param  saddr: Start address
  * @param  eaddr: End address
  * @retval CMD_SUCCESS or CMD_FAILED
  ***********************************************************************************************************/
static u32 _IAP_EXT_Erase(u32 type, u32 saddr, u32 eaddr)
{
  return CMD_SUCCESS;
}

/*********************************************************************************************************//**
  * @brief  Blank external flash.
  * @param  saddr: Start address
  * @param  length: Length
  * @retval CMD_SUCCESS or CMD_FAILED
  ***********************************************************************************************************/
static u32 _IAP_EXT_FlashBlank(u32 saddr, u32 length)
{
  return CMD_SUCCESS;
}

/*********************************************************************************************************//**
  * @brief  Verify external flash.
  * @param  saddr: Start address
  * @param  length: Length
  * @param  pbuffer: point of data buffer
  * @retval CMD_SUCCESS or CMD_FAILED
  ***********************************************************************************************************/
static u32 _IAP_EXT_FlashVerify(u32 saddr, u32 length, u8 *pbuffer)
{
  return CMD_SUCCESS;
}

/*********************************************************************************************************//**
  * @brief  Download image into external flash for program or verify.
  * @param  type: Program or verify
  *         @arg CMD_PROGRAM: Program mode
  *         @arg CMD_VERIFY: Verify mode
  *         @arg CMD_BLANK: Blank check mode
  *         @arg CMD_READ: Read mode
  * @param  saddr: Start address
  * @param  eaddr: End address
  * @retval CMD_SUCCESS or CMD_FAILED
  ***********************************************************************************************************/
static u32 _IAP_EXT_Flash(u32 type, u32 saddr, u32 eaddr)
{
  return CMD_SUCCESS;
}
#endif
#endif

/*********************************************************************************************************//**
  * @brief  Calculate CRC value.
  * @param  crc: Iinitial value of CRC (Usually as 0)
  * @param  saddr: Start address
  * @param  length: Length for CRC calculation
  * @retval Always success (CMD_SUCCESS)
  ***********************************************************************************************************/
static u32 _IAP_CRC(u32 crc, u32 saddr, u32 length)
{
  u32 uResult = CMD_SUCCESS;

  saddr += FLASH_BASE;

  __DBG_IAPPrintf("C\r\n");
  saddr &= ~ 0x03; // 4-byte align
  length = (length > 16) ? length : 16;
  crc = CRC16(crc, (u8 *)saddr, length);

  #if (DIGEST_CHECK_EN == 1)
  {
    u32 uDigestReceived;
    uDigestReceived = (u16)(gpCmdBuffer[12] | (gpCmdBuffer[13] << 8));
    guDigestResult = CRC16(guDigestResult, (u8*)Encrypt_Key, ARC4_KEY_LEN);
    crc = CRC16(crc, (u8*)Encrypt_Key, ARC4_KEY_LEN);

    __DBG_IAPPrintf("C %x %x %x %x %x\r\n", (unsigned int)saddr, (unsigned int)length, (unsigned int)crc, (unsigned int)uDigestReceived, guDigestResult);

    if (guDigestResult == uDigestReceived)
    {
      // Validation Digest
      gisDigestCheck = TRUE;
    }
    else
    {
      // Invalidation Digest
      // Erase AP code...
      _IAP_Erase(IAP_PAGE_ERASE, IAP_APFLASH_START, IAP_APFLASH_START + gAPLength);
      uResult = CMD_FAILED;
    }
  }
  #endif

  gResponseLength += 2;

  gpCmdBuffer[0] = crc;
  gpCmdBuffer[1] = crc >> 8;

  return uResult;
}

/*********************************************************************************************************//**
  * @brief  Send information to Host.
  * @retval Always success (CMD_SUCCESS)
  ***********************************************************************************************************/
static u32 _IAP_Info(void)
{
  __DBG_IAPPrintf("I\r\n");

  IAPUser_SendData((u8 *)gu32Infotable, 64);

  return CMD_SUCCESS;
}

/*********************************************************************************************************//**
  * @brief  Exit Loader mode.
  * @retval None
  ***********************************************************************************************************/
static void _IAP_Exit(void)
{
  #if 0 // Not apply for IAP mode
  while (1);
  #endif
}

#if (USER_CMD_DEMO == 1)
/*********************************************************************************************************//**
  * @brief  Download image for program or verify.
  * @param  uPar0: User define paramemter0 (8 bit)
  * @param  uPar1: User define paramemter1 (32 bit)
  * @param  uPar2: User define paramemter2 (32 bit)
  * @retval CMD_SUCCESS or CMD_FAILED
  ***********************************************************************************************************/
static u32 _User_Cmd50Example(u32 uPar0, u32 uPar1, u32 uPar2)
{
  switch (uPar0)
  {
    case 0x1:
    {
      (uPar1 == 1) ? ht32_led_on(LED2) : ht32_led_off(LED2);
      return CMD_SUCCESS;
    }
    case 0x2:
    {
      (uPar1 == 1) ? ht32_led_on(LED3) : ht32_led_off(LED3);
      return CMD_SUCCESS;
    }
    case 0x3:
    {
      (uPar1 == 1) ? ht32_led_on(LED4) : ht32_led_off(LED4);
      return CMD_SUCCESS;
    }
  }

  return CMD_FAILED;
}

/*********************************************************************************************************//**
  * @brief  Download image for program or verify.
  * @param  uPar0: User define paramemter0 (8 bit)
  * @param  uPar1: User define paramemter1 (32 bit)
  * @param  uPar2: User define paramemter2 (32 bit)
  * @retval CMD_SUCCESS or CMD_FAILED
  ***********************************************************************************************************/
static u32 _User_Cmd51Example(u32 uPar0, u32 uPar1, u32 uPar2)
{
  u8 *pPayload = (u8 *)(&gpCmdBuffer[CMD_PAYLOAD_ADDR]);
  u32 i;
  u32 uTxData[16];

  switch (uPar0)
  {
    case 0x0:
    {
      for (i = 0; i < CMD_PAYLOAD_LEN; i++)
      {
        uUserData[i] = *pPayload++;
      }
      return CMD_SUCCESS;
    }
    case 0x1:
    {
      for (i = 0; i < CMD_PAYLOAD_LEN; i += 4)
      {
        uTxData[i] = *((u32 *)(&uUserData[i]));
      }
      IAPUser_SendData((u8 *)uTxData, 64);
      return CMD_SUCCESS;
    }
  }

  return CMD_FAILED;
}
#endif

/*********************************************************************************************************//**
 * @brief  FUNCTIONDESCRIPTION.
 * @param  P1DESCRIPTION
 *         @arg ARGDESCRIPTION
 *         @arg ARGDESCRIPTION
 * @param  P2DESCRIPTION
 * @retval R1DESCRIPTION
 ************************************************************************************************************/
static void _ImageInfo_ActiveImage(u32 uCurrentAddress)
{
  u32 i;
  u32 *ptr = (u32 *)(uCurrentAddress + INFO_OFFSET);
  u32 uChecksum = 0;
  for (i = 0; i < 5; i++)
  {
    uChecksum += *(ptr + i);
  }
  flash_word_program(uCurrentAddress + INFO_OFFSET_CHECKSUM, uChecksum);
}

/*********************************************************************************************************//**
 * @brief  Calculate CRC for Application image.
 * @param  sLength: Length of the application image to be CRC-checked.
 * @retval Computed CRC value, or -1 if size exceeds AP flash range.
 ************************************************************************************************************/
static s32 _IAP_CalcAPImageCRC(s32 sLength)
{
  u32 uCRCResult;
  /* !!! NOTICE !!!
     "uPadding" value : Please confirm the empty vector value of startup.s file.
  */
  u32 uPadding = 0xFFFFFFFF;

  if((sLength < 0) || (sLength > LOADER_FLASH_SIZE))
  {
    return -1;  /* Image size out of valid AP flash range */
  }

  #if (IAP_CRC_MODE == 1) // 1: Hardware CRC, 0: Software CRC (If Hardware CRC not supported, Software CRC is always used.)
  crm_periph_clock_enable(CRM_CRC_PERIPH_CLOCK, TRUE);

  uCRCResult = CRC_CCITT(0, (u8 *)IAP_APFLASH_START, 40);
  uCRCResult = CRC_CCITT(uCRCResult, (u8 *)&uPadding, 4);
  uCRCResult = CRC_CCITT(uCRCResult, (u8 *)IAP_APFLASH_START + 44, 8);
  uCRCResult = CRC_CCITT(uCRCResult, (u8 *)&uPadding, 4);
  uCRCResult = CRC_CCITT(uCRCResult, (u8 *)IAP_APFLASH_START + 56, sLength - 56);
  #else
  /* Bit 31 ~ 16 match the IAP_CRC_KEY, inform IAP loader to check AP CRC value before start application*/
  uCRCResult = CRC16(0, (u8 *)IAP_APFLASH_START, 40);
  uCRCResult = CRC16(uCRCResult, (u8 *)&uPadding, 4);
  uCRCResult = CRC16(uCRCResult, (u8 *)IAP_APFLASH_START + 44, 8);
  uCRCResult = CRC16(uCRCResult, (u8 *)&uPadding, 4);
  uCRCResult = CRC16(uCRCResult, (u8 *)IAP_APFLASH_START + 56, sLength - 56);
  #endif
  return uCRCResult;
}

#if (IAP_DEBUG_MODE == 1)
/*********************************************************************************************************//**
  * @brief  Dump data for debug.
  * @param  memory
  * @param  Len
  * @retval None
  ***********************************************************************************************************/
static void __DBG_IAP_DumpData(uc8 *memory, u32 len)
{
  u32 i;
  for (i = 0; i < len; i++)
  {
    if (i % 8 == 0)
    {
      if (i != 0)
      {
        __DBG_IAPPrintf("\r\n");
      }
      __DBG_IAPPrintf("\t\t");
    }
    __DBG_IAPPrintf("%02X ", *((u8 *)(memory + i)));
  }
  __DBG_IAPPrintf("\r\n");

  return;
}
#endif


/**
  * @}
  */

/**
  * @}
  */

/**
  * @}
  */

/**
  * @}
  */
