/**
  ******************************************************************************
  * @file    openbl_usart_cmd.c
  * @author  MCD Application Team
  * @brief   Contains USART protocol commands
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2023 Puya Semiconductor Co.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by Puya under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "openbl_mem.h"
#include "openbl_usart_cmd.h"
#include "openbootloader_conf.h"
#include "flash_interface.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define OPENBL_USART_COMMANDS_NB          12U       /* Number of supported commands */

#define USART_RAM_BUFFER_SIZE             1024U     /* Size of USART buffer used to store received data from the host */

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Buffer used to store received data from the host */
static uint8_t USART_RAM_Buf[USART_RAM_BUFFER_SIZE];

/* Private function prototypes -----------------------------------------------*/
static uint8_t OPENBL_USART_GetCommandOpcode(void);
static uint8_t OPENBL_USART_GetAddress(uint32_t *Address);

static void OPENBL_USART_BeforeCommandProcess(uint8_t cmd);
static void OPENBL_USART_AfterCommandProcess(uint8_t cmd);
static void OPENBL_USART_CommandProcessing(uint8_t cmd);

static void OPENBL_USART_GetCommand(void);
static void OPENBL_USART_GetVersion(void);
static void OPENBL_USART_GetID(void);
static void OPENBL_USART_ReadMemory(void);
static void OPENBL_USART_WriteMemory(void);
static void OPENBL_USART_EraseMemory(void);
static void OPENBL_USART_ExtEraseMemory(void);
static void OPENBL_USART_Go(void);
static void OPENBL_USART_ReadoutProtect(void);
static void OPENBL_USART_ReadoutUnprotect(void);
static void OPENBL_USART_WriteProtect(void);
static void OPENBL_USART_WriteUnprotect(void);

/* Exported variables --------------------------------------------------------*/
/* Exported functions---------------------------------------------------------*/
/**
  * @brief  This function is used to get the command opcode from the given interface and execute the right command.
  * @retval None.
  */
void OPENBL_USART_CommandProcess(void)
{
  uint8_t command_opcode;

  /* Get the user command opcode */
  command_opcode = OPENBL_USART_GetCommandOpcode();

  OPENBL_USART_BeforeCommandProcess(command_opcode);
  OPENBL_USART_CommandProcessing(command_opcode);
  OPENBL_USART_AfterCommandProcess(command_opcode);
}

static void OPENBL_USART_BeforeCommandProcess(uint8_t cmd)
{
  switch (cmd)
  {
    case CMD_EXT_WRITE_MEMORY:
    case CMD_ERASE_MEMORY:
    case CMD_EXT_ERASE_MEMORY:
    case CMD_READ_PROTECT:
    case CMD_READ_UNPROTECT:
    case CMD_WRITE_PROTECT:
    case CMD_WRITE_UNPROTECT:

      /* Unlock the flash memory for write operation */
      OPENBL_FLASH_Unlock();
      break;
  }
}

static void OPENBL_USART_AfterCommandProcess(uint8_t cmd)
{
  switch (cmd)
  {
    case CMD_EXT_WRITE_MEMORY:
    case CMD_ERASE_MEMORY:
    case CMD_EXT_ERASE_MEMORY:
    case CMD_READ_PROTECT:
    case CMD_READ_UNPROTECT:
    case CMD_WRITE_PROTECT:
    case CMD_WRITE_UNPROTECT:

      /* Lock the Flash to disable the flash control register access */
      OPENBL_FLASH_Lock();
      break;
  }
}

static void OPENBL_USART_CommandProcessing(uint8_t cmd)
{
  switch (cmd)
  {
    case CMD_GET_COMMAND:
      OPENBL_USART_GetCommand();
      break;

    case CMD_GET_VERSION:
      OPENBL_USART_GetVersion();
      break;

    case CMD_GET_ID:
      OPENBL_USART_GetID();
      break;

    case CMD_READ_MEMORY:
      OPENBL_USART_ReadMemory();
      break;

    case CMD_EXT_WRITE_MEMORY:
      OPENBL_USART_WriteMemory();
      break;

    case CMD_ERASE_MEMORY:
      OPENBL_USART_EraseMemory();
      break;

    case CMD_EXT_ERASE_MEMORY:
      OPENBL_USART_ExtEraseMemory();
      break;

    case CMD_GO:
      OPENBL_USART_Go();
      break;

    case CMD_READ_PROTECT:
      OPENBL_USART_ReadoutProtect();
      break;

    case CMD_READ_UNPROTECT:
      OPENBL_USART_ReadoutUnprotect();
      break;

    case CMD_WRITE_PROTECT:
      OPENBL_USART_WriteProtect();
      break;

    case CMD_WRITE_UNPROTECT:
      OPENBL_USART_WriteUnprotect();
      break;

    /* Unknown command opcode */
    default:
      OPENBL_WriteByte(NACK_BYTE);
      break;
  }
}

/* Private functions ---------------------------------------------------------*/
/**
 * @brief  This function is used to get the command opcode from the host.
 * @retval Returns the command.
 */
static uint8_t OPENBL_USART_GetCommandOpcode(void)
{
  uint8_t command_opc[2];

  /* Get the command opcode */
  OPENBL_Read(command_opc, 2);

  /* Check the data integrity */
  if ((command_opc[0] ^ command_opc[1]) != 0xFF)
  {
    return ERROR_COMMAND;
  }
  else
  {
    return command_opc[0];
  }
}

/**
  * @brief  This function is used to get the list of the available USART commands
  * @retval None.
  */
static void OPENBL_USART_GetCommand(void)
{
  const uint8_t SendBuffer[OPENBL_USART_COMMANDS_NB + 3] =
  {
    OPENBL_USART_COMMANDS_NB,    /* The number of commands supported by the USART protocol */
    OPENBL_USART_VERSION,        /* USART protocol version */

    /* The list of supported commands */
    CMD_GET_COMMAND,             /* 1 */
    CMD_GET_VERSION,             /* 2 */
    CMD_GET_ID,                  /* 3 */
    CMD_READ_MEMORY,             /* 4 */
    CMD_GO,                      /* 5 */
    CMD_EXT_WRITE_MEMORY,        /* 6 */
    CMD_ERASE_MEMORY,            /* 7 */
    CMD_EXT_ERASE_MEMORY,        /* 8 */
    CMD_WRITE_PROTECT,           /* 9 */
    CMD_WRITE_UNPROTECT,         /* 10 */
    CMD_READ_PROTECT,            /* 11 */
    CMD_READ_UNPROTECT,          /* 12 */

    ACK_BYTE,                    /* Last Acknowledge synchronization byte */
  };

  /* Send Acknowledge byte to notify the host that the command is recognized */
  OPENBL_WriteByte(ACK_BYTE);

  OPENBL_Write((uint8_t *)SendBuffer, sizeof(SendBuffer));
}

/**
  * @brief  This function is used to get the USART protocol version.
  * @retval None.
  */
static void OPENBL_USART_GetVersion(void)
{
  const uint8_t SendBuffer[4] =
  {
    OPENBL_USART_VERSION,        /* USART protocol version */
    DEVICE_ID_MSB,
    DEVICE_ID_LSB,

    ACK_BYTE,                    /* Last Acknowledge synchronization byte */
  };

  /* Send Acknowledge byte to notify the host that the command is recognized */
  OPENBL_WriteByte(ACK_BYTE);

  OPENBL_Write((uint8_t *)SendBuffer, sizeof(SendBuffer));
}

/**
  * @brief  This function is used to get the device ID.
  * @retval None.
  */
static void OPENBL_USART_GetID(void)
{
  const uint8_t SendBuffer[4] =
  {
    1,
    DEVICE_ID_MSB,
    DEVICE_ID_LSB,

    ACK_BYTE,                    /* Last Acknowledge synchronization byte */
  };
  /* Send Acknowledge byte to notify the host that the command is recognized */
  OPENBL_WriteByte(ACK_BYTE);

  OPENBL_Write((uint8_t *)SendBuffer, sizeof(SendBuffer));
}

/**
 * @brief  This function is used to read memory from the device.
 * @retval None.
 */
static void OPENBL_USART_ReadMemory(void)
{
  uint32_t address;
  uint32_t number;
  uint8_t data[2];

  OPENBL_WriteByte(ACK_BYTE);

  /* Get the memory address */
  if (OPENBL_USART_GetAddress(&address) == NACK_BYTE)
  {
    OPENBL_WriteByte(NACK_BYTE);
  }
  else
  {
    OPENBL_WriteByte(ACK_BYTE);

    /* Get the number of bytes to be received */
    OPENBL_Read(data, 2);

    /* Check data integrity */
    if ((data[0] ^ data[1]) != 0xFF)
    {
      OPENBL_WriteByte(NACK_BYTE);
    }
    else
    {
      OPENBL_WriteByte(ACK_BYTE);

      /* Read the data (data + 1) from the memory and send them to the host */
      number = (uint32_t)data[0] + 1U;

      OPENBL_MEM_Read(address, USART_RAM_Buf, number);

      OPENBL_Write(USART_RAM_Buf, number);
    }
  }
}

/**
 * @brief  This function is used to write in to device memory.
 * @retval None.
 */
static void OPENBL_USART_WriteMemory(void)
{
  uint32_t address;
  uint32_t tmpXOR;
  uint32_t number;
  uint32_t mem_area;
  uint8_t data[2];

  OPENBL_WriteByte(ACK_BYTE);

  /* Get the memory address */
  if (OPENBL_USART_GetAddress(&address) == NACK_BYTE)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  OPENBL_WriteByte(ACK_BYTE);

  /* Read the number of bytes to be written: Max number of data = 0xFFFF + 1 = 65536 */
  OPENBL_Read(data, 2);

  /* Number of data to be written = data + 1 */
  number = ((data[0] << 8) | data[1]) + 1U;

  if (number > 0x200)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  /* Checksum Initialization */
  tmpXOR = data[0] ^ data[1];

  /* UART receive data and send to RAM Buffer */
  OPENBL_Read(USART_RAM_Buf, number + 1);

  for (uint32_t i = 0; i < number; i++)
  {
    tmpXOR ^= USART_RAM_Buf[i];
  }

  /* Send NACk if Checksum is incorrect */
  if (tmpXOR != USART_RAM_Buf[number])
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  /* Write data to memory */
  OPENBL_MEM_Write(address, (uint8_t *)USART_RAM_Buf, number);

  /* Send last Acknowledge synchronization byte */
  OPENBL_WriteByte(ACK_BYTE);

  /* Check if the received address is an option byte address */
  mem_area = OPENBL_MEM_GetAddressArea(address);

  if (mem_area == AREA_OB)
  {
    /* Launch Option Bytes reload */
    OPENBL_MEM_OptionBytesLaunch();
  }
}

/**
 * @brief  This function is used to erase a memory.
 * @retval None.
 */
static void OPENBL_USART_EraseMemory(void)
{
  ErrorStatus error_value = ERROR;
  uint32_t banksize = (((M32(0x1FFF7D00) & 0x3) + 1) * 128 * 1024) / 2;
  uint32_t startaddress;
  uint32_t endaddress;
  uint8_t data[2];
  uint8_t status = ACK_BYTE;

  OPENBL_WriteByte(ACK_BYTE);

  /* Get memory address */
  if (OPENBL_USART_GetAddress(&startaddress) == NACK_BYTE)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  OPENBL_WriteByte(ACK_BYTE);

  /* Get memory address */
  if (OPENBL_USART_GetAddress(&endaddress) == NACK_BYTE)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  OPENBL_WriteByte(ACK_BYTE);

  /* Read erase type */
  OPENBL_Read(data, 2);

  if ((data[0] ^ data[1]) != 0xFF)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  while(startaddress < endaddress)
  {
    switch(data[0])
    {
      case ERASE_1K:
        error_value = OPENBL_MEM_ErasePage(startaddress);
        startaddress += FLASH_PAGE_SIZE;

      /* fall through to erase 2 page(2 * 512) */

      case ERASE_PAGE:
        error_value = OPENBL_MEM_ErasePage(startaddress);
        startaddress += FLASH_PAGE_SIZE;
        break;

      case ERASE_SECTOR:
        error_value = OPENBL_MEM_EraseSector(startaddress);
        startaddress += FLASH_SECTOR_SIZE;
        break;

      case ERASE_BLOCK:
        error_value = OPENBL_MEM_EraseBlock(startaddress);
        startaddress += FLASH_BLOCK_SIZE;
        break;

      case ERASE_BANK:
        error_value = OPENBL_MEM_EraseBank(startaddress);
        startaddress += banksize;
        break;

      case ERASE_CHIP:
        error_value = OPENBL_MEM_EraseChip(FLASH_BASE);
        startaddress = endaddress;
        break;

      default:
        error_value = ERROR;
    }

    if (error_value != SUCCESS)
    {
      status = NACK_BYTE;
      break;
    }
  }

  OPENBL_WriteByte(status);
}

/**
 * @brief  This function is used to erase a memory.
 * @retval None.
 */
static void OPENBL_USART_ExtEraseMemory(void)
{
  uint32_t tmpXOR;
  uint32_t number;
  uint32_t address;
  uint32_t banksize = (((M32(0x1FFF7D00) & 0x3) + 1) * 128 * 1024) / 2;

  uint8_t data[2];
  ErrorStatus error_value = ERROR;
  uint8_t status = ACK_BYTE;

  /* Check if the memory is not protected */
  if (OPENBL_MEM_GetReadOutProtectionStatus() != RESET)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  OPENBL_WriteByte(ACK_BYTE);

  /* Read number of pages to be erased */
  OPENBL_Read(data, 2);

  /* Checksum initialization */
  tmpXOR  = data[0];
  tmpXOR ^= data[1];

  if (ERASE_CHIP == data[0])
  {
    if (tmpXOR != OPENBL_ReadByte())
    {
      OPENBL_WriteByte(NACK_BYTE);
      return;
    }

    error_value = OPENBL_MEM_EraseChip(FLASH_BASE);

    if (error_value != SUCCESS)
    {
      status = NACK_BYTE;
    }
  }
  else
  {
    /* Number of pages(sectors) to be erased (data + 1) */
    number = 2 * ((uint32_t)data[1] + 1U);

    OPENBL_Read(USART_RAM_Buf, number + 1);

    /* Get the pages(sectors) to be erased */
    for (uint32_t i = 0; i < number; i++)
    {
      tmpXOR ^= USART_RAM_Buf[i];
    }

    /* Check data integrity */
    if (tmpXOR != USART_RAM_Buf[number])
    {
      OPENBL_WriteByte(NACK_BYTE);
      return;
    }

    for (uint32_t i = 0; i < number; i += 2)
    {
      address = USART_RAM_Buf[i] << 8 | USART_RAM_Buf[i + 1];

      switch (data[0])
      {
        case ERASE_1K:
          address *= FLASH_PAGE_SIZE * 2;
          error_value = OPENBL_MEM_ErasePage(FLASH_START_ADDRESS + address);

          if (error_value != SUCCESS)
          {
            status = NACK_BYTE;
            break;
          }

          error_value = OPENBL_MEM_ErasePage(FLASH_START_ADDRESS + address + FLASH_PAGE_SIZE);
          break;

        case ERASE_PAGE:
          address *= FLASH_PAGE_SIZE;
          error_value = OPENBL_MEM_ErasePage(FLASH_START_ADDRESS + address);
          break;

        case ERASE_SECTOR:
          address *= FLASH_SECTOR_SIZE;
          error_value = OPENBL_MEM_EraseSector(FLASH_START_ADDRESS + address);
          break;

        case ERASE_BLOCK:
          address *= FLASH_BLOCK_SIZE;
          error_value = OPENBL_MEM_EraseBlock(FLASH_START_ADDRESS + address);
          break;

        case ERASE_BANK:
          address *= banksize;
          error_value = OPENBL_MEM_EraseBank(FLASH_START_ADDRESS + address);
          break;

        default:
          error_value = ERROR;
          break;
      }

      if (error_value != SUCCESS)
      {
        status = NACK_BYTE;
        break;
      }
    }
  }

  OPENBL_WriteByte(status);
}

/**
  * @brief  This function is used to jump to the user application.
  * @retval None.
  */
static void OPENBL_USART_Go(void)
{
  uint32_t address;
  uint32_t mem_area;

  /* Check memory protection then send adequate response */
  if (OPENBL_MEM_GetReadOutProtectionStatus() != RESET)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  OPENBL_WriteByte(ACK_BYTE);

  /* Get memory address */
  if (OPENBL_USART_GetAddress(&address) == NACK_BYTE)
  {
    OPENBL_WriteByte(NACK_BYTE);
  }
  else
  {
    /* Check if received address is valid or not */
    mem_area = OPENBL_MEM_GetAddressArea(address);

    if ((mem_area != AREA_FLASH) && (mem_area != AREA_RAM))
    {
      OPENBL_WriteByte(NACK_BYTE);
    }
    else
    {
      /* If the jump address is valid then send ACK */
      OPENBL_WriteByte(ACK_BYTE);

      OPENBL_MEM_JumpToAddress(address);
    }
  }
}

/**
 * @brief  This function is used to enable readout protection.
 * @retval None.
 */
static void OPENBL_USART_ReadoutProtect(void)
{
  /* Check memory protection then send adequate response */
  if (OPENBL_MEM_GetReadOutProtectionStatus() != RESET)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  OPENBL_WriteByte(ACK_BYTE);

  /* Enable the read protection */
  OPENBL_MEM_SetReadOutProtection(ENABLE);

  OPENBL_WriteByte(ACK_BYTE);

  /* Launch Option Bytes reload */
  OPENBL_MEM_OptionBytesLaunch();
}

/**
 * @brief  This function is used to disable readout protection.
 * @retval None.
 */
static void OPENBL_USART_ReadoutUnprotect(void)
{
  OPENBL_WriteByte(ACK_BYTE);

  /* Once the option bytes modification start bit is set in FLASH CR register,
     all the RAM is erased, this causes the erase of the Open Bootloader RAM.
     This is why the last ACK is sent before the call of OPENBL_MEM_SetReadOutProtection */
  OPENBL_WriteByte(ACK_BYTE);

  /* Disable the read protection */
  OPENBL_MEM_SetReadOutProtection(DISABLE);

  /* Launch Option Bytes reload and reset system */
  OPENBL_MEM_OptionBytesLaunch();
}

/**
 * @brief  This function is used to enable write protect.
 * @retval None.
 */
static void OPENBL_USART_WriteProtect(void)
{
  uint32_t counter;
  uint32_t length;
  uint32_t data;
  uint32_t xor;
  ErrorStatus error_value;
  uint8_t *ramaddress;

  /* Check if the memory is not protected */
  if (OPENBL_MEM_GetReadOutProtectionStatus() != RESET)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  OPENBL_WriteByte(ACK_BYTE);

  /* Get the data length */
  data = OPENBL_ReadByte();

  ramaddress = (uint8_t *) USART_RAM_Buf;
  length     = data + 1U;

  /* Checksum Initialization */
  xor = data;

  /* Receive data and write to RAM Buffer */
  for (counter = length; counter != 0U ; counter--)
  {
    data  = OPENBL_ReadByte();
    xor  ^= data;

    *(__IO uint8_t *)(ramaddress) = (uint8_t) data;

    ramaddress++;
  }

  /* Check data integrity and send NACK if Checksum is incorrect */
  if (OPENBL_ReadByte() != (uint8_t) xor)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  ramaddress = (uint8_t *) USART_RAM_Buf;

  /* Enable the write protection */
  error_value = OPENBL_MEM_SetWriteProtection(ENABLE, ramaddress, length);

  if (error_value != SUCCESS)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  OPENBL_WriteByte(ACK_BYTE);
  OPENBL_MEM_OptionBytesLaunch();
}

/**
 * @brief  This function is used to disable write protect.
 * @retval None.
 */
static void OPENBL_USART_WriteUnprotect(void)
{
  ErrorStatus error_value;

  /* Check if the memory is not protected */
  if (OPENBL_MEM_GetReadOutProtectionStatus() != RESET)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  OPENBL_WriteByte(ACK_BYTE);

  /* Disable write protection */
  error_value = OPENBL_MEM_SetWriteProtection(DISABLE, NULL, 0);

  if (error_value != SUCCESS)
  {
    OPENBL_WriteByte(NACK_BYTE);
    return;
  }

  OPENBL_WriteByte(ACK_BYTE);
  OPENBL_MEM_OptionBytesLaunch();
}

/**
 * @brief  This function is used to get a valid address.
 * @retval Returns NACK status in case of error else returns ACK status.
 */
static uint8_t OPENBL_USART_GetAddress(uint32_t *Address)
{
  uint8_t addr[5];
  uint8_t tmpXOR;
  uint32_t mem_area;

  OPENBL_Read(addr, 5);

  tmpXOR = addr[0] ^ addr[1] ^ addr[2] ^ addr[3];

  /* Check the integrity of received data */
  if (addr[4] != tmpXOR)
  {
    return NACK_BYTE;
  }

  *Address = ((uint32_t)addr[0] << 24) | ((uint32_t)addr[1] << 16) | ((uint32_t)addr[2] << 8) | (uint32_t)addr[3];

  /* Check if received address is valid or not */
  mem_area = OPENBL_MEM_GetAddressArea(*Address);

  if(mem_area == AREA_ERROR)
  {
    return NACK_BYTE;
  }

  if(mem_area == AREA_FLASH || mem_area == AREA_RAM)
  {
    /* Check if the memory is not protected */
    if (OPENBL_MEM_GetReadOutProtectionStatus() != RESET)
    {
      return NACK_BYTE;
    }
  }

  return ACK_BYTE;
}
/************************ (C) COPYRIGHT Puya *****END OF FILE******************/
