/*****************************************************************************
 * Copyright (c) 2019, Nations Technologies Inc.
 *
 * All rights reserved.
 * ****************************************************************************
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Nations' name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY NATIONS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL NATIONS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************/
/** ****************************************************************************
 * @file     bootloader_cmd.c
 * @author
 * @date
 * @version  v1.0.0
 * @brief
  *
 ******************************************************************************/
#include "bootloader_cmd.h"
#include "n32a003_crc.h"
#include "n32a003_flash.h"
#include "n32a003_uart.h"
#include "n32a003.h"

/* Private typedef -----------------------------------------------------------*/
typedef  void (*pFunction)(void);
/* Private define ------------------------------------------------------------*/

#define UART_MAX_BR              ((uint32_t)0x000E15C4) /* Max 923076bps */
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
PC_CMD_Type pc_cmd;
IC_CMD_Type ic_cmd;
uint8_t cmd_return_cr;

const uint8_t Return_Info_CR[25][2] =
{
  {0xA0,0x00},{0xB0,0x00},{0xB0,0x10},{0xB0,0x11},{0xB0,0x12},{0xB0,0x13},
  {0xB0,0x20},{0xB0,0x21},{0xB0,0x30},{0xB0,0x31},{0xB0,0x32},{0xB0,0x33},
  {0xB0,0x34},{0xB0,0x35},{0xB0,0x36},{0xB0,0x37},{0xB0,0x38},{0xB0,0x39},
  {0xB0,0x3A},{0xB0,0x3B},{0xB0,0x3C},{0xB0,0x3D},{0xB0,0x3E},{0xB0,0x3F},
  {0xBB,0xCC}
};
 uint32_t UART1_BaudRate = DEFAULT_BAUD_RATE;
/* Private function prototypes -----------------------------------------------*/
static uint32_t tagUserApp(void);
static uint32_t calcBootCRC(void);
static FLASH_STS BOOT_EraseProgramOptionBytes(uint32_t *data);
/* Private functions ---------------------------------------------------------*/

 /**
  * @brief  BOOT_CMD.
  * @param
  * @retval
  */
void BOOT_CMD(void)
{
  uint32_t i;
  uint8_t index;
  uint32_t tmp_addr,tmp_len,tmp_CRC;

  //Feed WDG if enabled
  if(!(FLASH->OB & FLASH_OB_WDG_SW))
  {
    IWDG->KEY = 0xAAAA;
  }

  if(!pc_cmd.Flag) return;
  pc_cmd.Flag = false;

#ifdef N32A003_BOOT_DEBUG
  printf("\r\n\r\nCmdRaw:");
#endif

  uint32_t len = 8 + pc_cmd.LEN;
  uint8_t  xor = UART_STA1^UART_STA2;
  for(uint32_t i = 0; i< len; i++)
  {
#ifdef N32A003_BOOT_DEBUG
    printf(" %02X",((uint8_t*)&pc_cmd)[i]);
#endif
    xor ^= ((uint8_t*)&pc_cmd)[i];
  }

#ifdef N32A003_BOOT_DEBUG
  printf("\r\n");
#endif


  if(pc_cmd.XOR != xor)
  {
#ifdef N32A003_BOOT_DEBUG
    printf("XOR *SHOULD* be 0x%02X\r\n", xor);
#endif
    return;
  }

#ifdef N32A003_BOOT_DEBUG
  printf("CmdMsg: CMD1: 0x%02X CMD2: 0x%02X LEN: 0x%04X XOR: 0x%02X\r\n", pc_cmd.CMD_H, pc_cmd.CMD_L, pc_cmd.LEN, pc_cmd.XOR);
#endif

  ic_cmd.CMD_H = pc_cmd.CMD_H;
  ic_cmd.CMD_L = index = pc_cmd.CMD_L;
  cmd_return_cr = CMD_CR_SUCCESS;
  ic_cmd.LEN = 0x0000;

  switch(pc_cmd.CMD_H)
  {
    case CMD_SET_BR:
      tmp_addr = pc_cmd.Par[0]<<24 | pc_cmd.Par[1]<<16 | pc_cmd.Par[2]<<8 | pc_cmd.Par[3];
      cmd_return_cr = CMD_CR_FAILED;
      ic_cmd.LEN    = CMD_LEN_BR;
      if(tmp_addr <= UART_MAX_BR)
      {
        UART1_BaudRate = tmp_addr;
        cmd_return_cr = CMD_CR_SUCCESS;
      }
      break;

    case CMD_GET_INF:
      ic_cmd.Data[0] = 0x07; //Chip Type Code
      ic_cmd.Data[1] = BOOT_CMD_SET_VERSION; //BCD Code
      ic_cmd.Data[2] =(BOOT_VERSION_MAJOR << 4)|BOOT_VERSION_MINOR; //BCD Code

      //16-Byte UCID
      for(uint32_t i = 0; i < 16; i++)
      {
        ic_cmd.Data[3+i] = *(uint8_t*)(UCID_ADDR+i);
      }

      //12-Byte UID
      for(uint32_t i = 0; i < 12; i++)
      {
        ic_cmd.Data[19+i] = *(uint8_t*)(UID_ADDR+i);
      }

      //4-Byte DBGMCU_ID
      for(uint32_t i = 0; i < 4; i++)
      {
        ic_cmd.Data[31+i] = *(uint8_t*)(DBGMCU_ID_ADDR+i);
      }

      ic_cmd.LEN = 35;
      break;

    case CMD_FLASH_ERASE:
      tmp_addr = USER_APP_START_ADDR + FLASH_Page_Length*((uint32_t)pc_cmd.Par[0] + ((uint32_t)pc_cmd.Par[1] << 8));
      tmp_len = FLASH_Page_Length*((uint32_t)pc_cmd.Par[2] + ((uint32_t)pc_cmd.Par[3] << 8));
      if((cmd_return_cr = FLASH_Range_Configuration(tmp_addr, tmp_len, index)) != CMD_CR_SUCCESS)
      {
        break;
      }

      FLASH_Unlock();
      FLASH->STS = FLASH->STS;
      for(i = 0; i < tmp_len; i += FLASH_Page_Length)
      {
        uint8_t status = FLASH_One_Page_Erase(tmp_addr + i);
        if(status != FLASH_EOP)
        {
          cmd_return_cr = (status == FLASH_ERR_WRP) ? CMD_CR_WRP : CMD_CR_FAILED;
          break;
        }
      }
      FLASH_Lock();
      break;

    case CMD_FLASH_DWNLD:
      if(pc_cmd.CMD_L == 0xF0)
      {
        if(tagUserApp())
        {
          cmd_return_cr = CMD_CR_FAILED;
        }
        break;
      }

      tmp_addr = *(uint32_t*)&pc_cmd.Par[0];
      tmp_len = pc_cmd.LEN - 20; //16-byte DAT[0:15] and 4-byte 32-bit CRC16.
      if(tmp_len > 0x80)
      {
        cmd_return_cr = CMD_CR_FAILED;
        break;
      }
      if((cmd_return_cr = FLASH_Range_Configuration(tmp_addr, tmp_len, index)) != CMD_CR_SUCCESS)
      {
        break;
      }

      //payload data
      if(CRC16_Buffer_Calculate(&pc_cmd.Data[16], tmp_len) != *(uint32_t*)&pc_cmd.Data[16+tmp_len])
      {
        cmd_return_cr = CMD_CR_FAILED;
        break;
      }

      FLASH_Unlock();
      FLASH->STS = FLASH->STS;
      for(i = 0; i < tmp_len; i+=4)
      {
        uint8_t status = FLASH_Word_Program(tmp_addr+i, *(uint32_t*)&pc_cmd.Data[16+i]);
        if(status != FLASH_EOP)
        {
          cmd_return_cr = (status == FLASH_ERR_WRP) ? CMD_CR_WRP : CMD_CR_PROGRAMERR;
          break;
        }
      }
      FLASH_Lock();
      break;

    case CMD_DATA_CRC_CHECK:
      tmp_CRC  = *(uint32_t*)&pc_cmd.Par[0];
      tmp_addr = *(uint32_t*)&pc_cmd.Data[16];
      tmp_len  = *(uint32_t*)&pc_cmd.Data[20];

      if(tmp_len < FLASH_Page_Length)
      {
        cmd_return_cr = CMD_CR_LENERR;
        break;
      }

      if((cmd_return_cr = FLASH_Range_Configuration(tmp_addr, tmp_len, index)) != CMD_CR_SUCCESS)
      {
        //Status word will be returned.
        break;
      }

      if(tmp_CRC != CRC16_Buffer_Calculate((uint8_t *)tmp_addr, tmp_len))
      {
        cmd_return_cr = CMD_CR_CRCERR;
      }
      break;

    case CMD_DATA_READ:
      if(pc_cmd.LEN != 0x0001)
      {
        cmd_return_cr = CMD_CR_FAILED;
        break;
      }
      tmp_addr = *(uint32_t*)&pc_cmd.Par[0];
      tmp_len  = pc_cmd.Data[0];
      cmd_return_cr = FLASH_Range_Configuration(tmp_addr, tmp_len, 0);
      if(cmd_return_cr != CMD_CR_SUCCESS) break;

      for(uint32_t i = 0; i < tmp_len; i += 4)
      {
        *((uint32_t*)&ic_cmd.Data[i]) = *((uint32_t*)(tmp_addr+i));
      }
      *((uint32_t*)&ic_cmd.Data[tmp_len]) = CRC16_Buffer_Calculate((uint8_t *)tmp_addr, tmp_len);
      ic_cmd.LEN = tmp_len + 4;
      break;

    case CMD_OPT_RW:
      if(pc_cmd.CMD_L == OPT_CFG || pc_cmd.CMD_L == OPT_CFG_RESET)
      {
        FLASH_Unlock();
        if(BOOT_EraseProgramOptionBytes((uint32_t *)pc_cmd.Data) != FLASH_EOP)
        {
          cmd_return_cr = CMD_CR_FAILED;
        }
        FLASH_Lock();
      }

      ic_cmd.LEN = CMD_LEN_OPT_RW;
      *(uint32_t *)(&ic_cmd.Data[0])  = OBT->USER_RDP;
      *(uint32_t *)(&ic_cmd.Data[4])  = OBT->Data1_Data0;
      *(uint32_t *)(&ic_cmd.Data[8])  = OBT->USER3_USER2;
      *(uint32_t *)(&ic_cmd.Data[12]) = OBT->USER4_RDP2;
      break;

    case CMD_SYS_RESET:
      break;
    case CMD_APP_GO:
      break;
    default:
        cmd_return_cr = CMD_CR_FAILED;
      break;
  }

  ic_cmd.CR1 = Return_Info_CR[cmd_return_cr][0];
  ic_cmd.CR2 = Return_Info_CR[cmd_return_cr][1];

#ifdef N32A003_BOOT_DEBUG
  printf("ResRaw:");
  for(uint32_t i = 0; i < ic_cmd.LEN + 4; i++)
  {
    printf(" %02X", ((uint8_t*)(&ic_cmd))[i]);
  }
  printf("\r\n");
#endif

  Uart_Send(4 + ic_cmd.LEN, (uint8_t *)&ic_cmd);
  BOOT_End();
}

 /**
  * @name  BOOT_End()
  * @brief Do some special treatment exactly following normal command handling.
  * @param
  * @retval
  */
void BOOT_End(void)
{
  if(cmd_return_cr == CMD_CR_SUCCESS)
  {
    uint32_t Delay = 0x10000;
    while(Delay--);

    if(pc_cmd.CMD_H == CMD_SET_BR)
    {
      UART_Baud_Rate_Config(UART1, UART1_BaudRate);
    }
        
    if(((pc_cmd.CMD_H == CMD_OPT_RW) && (pc_cmd.CMD_L == OPT_CFG_RESET)) ||
      (pc_cmd.CMD_H == CMD_SYS_RESET)||
      (pc_cmd.CMD_H == CMD_APP_GO))
    {
      NVIC_SystemReset();
    }
    
    if(pc_cmd.CMD_H == CMD_APP_GO)
    {
      runUserApp();
    }

    
  }
}

/**
 * @name  checkBootCRC()
 * @brief Check Boot CRC
 * @param
 * @retval 0: OK; others: error.
 */
uint32_t checkBootCRC(void)
{
  uint32_t crc1 = *(uint32_t*)BOOT_CRCCHCK_ADDR;
  uint32_t crc2 = calcBootCRC();

#ifdef N32A003_BOOT_DEBUG
  (void)crc1;
  return 0;
#else
  return (crc1 == 0xFFFFFFFF || crc1 == crc2) ? 0:1;
#endif
}

/**
 * @name  calcBootCRC()
 * @brief Calculate 32-bit CRC16 value
 * @param
 * @retval
 */
static uint32_t calcBootCRC(void)
{
  uint32_t idx;

  CRC->CRC16D = 0x0000;
  for(idx = FLASH_Address_Start; idx < BOOT_CRCCHCK_ADDR; idx++)
  {
    CRC->CRC16DAT = *(uint8_t*)idx;
  }

  return CRC->CRC16D;
}

/**
  * @name  tagUserApp()
  * @brief Tag on a reserved address after user application is downloaded.
  * @param
  * @retval 0: OK; others: error.
  */

static uint32_t tagUserApp(void)
{
  if(*(uint32_t*)BOOT_APP_FLAG1_ADDR ==  USER_APP_DWNLD_FLAG && 
     *(uint32_t*)BOOT_APP_FLAG2_ADDR == ~USER_APP_DWNLD_FLAG) return 0; //OK, already tagged.

  if(*(uint32_t*)BOOT_APP_FLAG1_ADDR != 0xFFFFFFFF ||
     *(uint32_t*)BOOT_APP_FLAG2_ADDR != 0xFFFFFFFF)
  {
      FLASH_Unlock();
      FLASH->STS = FLASH->STS;
      if(FLASH_One_Page_Erase(BOOT_APP_FLGPG_ADDR) != FLASH_EOP) return 1;
      FLASH_Lock();
  }

  FLASH_Unlock();
  if(FLASH_Word_Program(BOOT_APP_FLAG1_ADDR,  USER_APP_DWNLD_FLAG) != FLASH_EOP ||
     FLASH_Word_Program(BOOT_APP_FLAG2_ADDR, ~USER_APP_DWNLD_FLAG) != FLASH_EOP)
  {
    return 1;
  }
  FLASH_Lock();

  return 0;
}

 /**
  * @name  runUserApp()
  * @brief Run user application.
  * @param
  * @retval
  */

void runUserApp()
{
  if(*(uint32_t*)BOOT_APP_FLAG1_ADDR ==  USER_APP_DWNLD_FLAG &&
     *(uint32_t*)BOOT_APP_FLAG2_ADDR == ~USER_APP_DWNLD_FLAG)
  {
    uint32_t addr = USER_APP_START_ADDR;

    //Turn off interrupt
     NVIC->ICER[0] = 0xFFFFFFFE; 
     NVIC->ICPR[0] = 0xFFFFFFFE; 
    //Initialize user application's Stack Pointer and PC
    __set_MSP(*(__IO uint32_t *)(addr));
    pFunction Jump_To_Application = (pFunction)(*(__IO uint32_t *)(addr + 4));

    // Vector Table Relocation
    FLASH->VTOR = 0x80000000 | addr;
    Jump_To_Application();
  }
}

 /**
  * @name  BOOT_EraseProgramOptionBytes()
  * @brief First erase option bytes and then program new ones. 
  * @param
  * @retval
  */
static FLASH_STS BOOT_EraseProgramOptionBytes(uint32_t *data)
{
  FLASH_STS sts = FLASH_Option_Bytes_Erase();
  if(sts != FLASH_EOP) return sts;

  Option_Bytes_Unlock();
  FLASH_Flag_Status_Clear(FLASH_STS_CLRFLAG);
  FLASH_Last_Operation_Wait(PROGRAM_TIMEOUT);
  FLASH->CTRL |= FLASH_CTRL_SET_OPTPG;
  for(uint32_t i = 0; i < 4; i++)
  {
    *(((__IO uint32_t*)(&OBT->USER_RDP))+i) = *(data+i);
    FLASH_Last_Operation_Wait(PROGRAM_TIMEOUT);
  }

  FLASH->CTRL &= FLASH_CTRL_RESET_OPTPG;
  Option_Bytes_Lock();

  return sts;
}

 /**
  * @name  FLASH_Range_Configuration()
  * @brief KEY Certification.
  * @param addr:
  * @param size:
  * @param region: reaserved at present
  * @retval
  */
CMD_RETURN_CR FLASH_Range_Configuration(uint32_t addr, uint32_t size, uint8_t region)
{
  if(addr%16)
  {
    return CMD_CR_ADDERR;
  }

  if((size%16) || (size == 0))
  {
    return CMD_CR_LENERR;
  }

  if((addr < USER_APP_START_ADDR) || (addr + size - 1 > USER_APP_END_ADDR))
  {
    return CMD_CR_FLASHLIMET;
  }

  //RDPL1/2 protection
  if(((FLASH->OB & ((uint32_t)0x80000002)) != (uint32_t)RESET)
   && (addr < FLASH_RDP_END_ADDR) && (pc_cmd.CMD_H != CMD_DATA_CRC_CHECK))
  {
    return CMD_CR_RDP;
  }

  return CMD_CR_SUCCESS;
}
