/**
  @Generated 16-bit Bootloader Source File

  @Company:
    Microchip Technology Inc.

  @File Name: 
    boot_process.c

  @Summary:
    This is the boot_process.c file generated using 16-bit Bootloader

  @Description:
    This header file provides implementations for driver APIs for all modules selected in the GUI.
    Generation Information :
        Product Revision  :  16-bit Bootloader - 1.17.3
        Device            :  PIC24FJ1024GA606
    The generated drivers are tested against the following:
        Compiler          :  XC16 v1.36B
        MPLAB             :  MPLAB X v5.15
*/
/*
Copyright (c) [2012-2019] Microchip Technology Inc.  

    All rights reserved.

    You are permitted to use the accompanying software and its derivatives 
    with Microchip products. See the Microchip license agreement accompanying 
    this software, if any, for additional info regarding your rights and 
    obligations.
    
    MICROCHIP SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT 
    WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT 
    LIMITATION, ANY WARRANTY OF MERCHANTABILITY, TITLE, NON-INFRINGEMENT 
    AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP OR ITS
    LICENSORS BE LIABLE OR OBLIGATED UNDER CONTRACT, NEGLIGENCE, STRICT 
    LIABILITY, CONTRIBUTION, BREACH OF WARRANTY, OR OTHER LEGAL EQUITABLE 
    THEORY FOR ANY DIRECT OR INDIRECT DAMAGES OR EXPENSES INCLUDING BUT NOT 
    LIMITED TO ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES, 
    OR OTHER SIMILAR COSTS. 
    
    To the fullest extend allowed by law, Microchip and its licensors 
    liability will not exceed the amount of fees, if any, that you paid 
    directly to Microchip to use this software. 
    
    THIRD PARTY SOFTWARE:  Notwithstanding anything to the contrary, any 
    third party software accompanying this software is subject to the terms 
    and conditions of the third party's license agreement.  To the extent 
    required by third party licenses covering such third party software, 
    the terms of such license will apply in lieu of the terms provided in 
    this notice or applicable license.  To the extent the terms of such 
    third party licenses prohibit any of the restrictions described here, 
    such restrictions will not apply to such third party software.
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

#include "com_adaptor.h"
#include "boot_private.h"
#include "boot_process.h"
#include "../memory/flash.h" 

#include "boot_config.h"
#include "boot_demo.h"

#ifndef FLASH_ERASE_PAGE_MASK 
#define FLASH_ERASE_PAGE_MASK  (~((FLASH_ERASE_PAGE_SIZE_IN_INSTRUCTIONS*2) - 1)) 
#endif


#if ((BOOT_CONFIG_MAX_PACKET_SIZE - SIZE_OF_CMD_STRUCT_0) < MINIMUM_WRITE_BLOCK_SIZE )
#error "The maximum packet size is not large enough to store a full write block plus header. Make the max packet size larger."
#endif

#if __DEBUG
// added for debug
extern uint32_t eraseCalls;
extern uint32_t maxEraseAddr;
extern uint32_t eraseCmdDataLen;
extern uint32_t eraseCmdAddr;
extern uint32_t eraseLimit;
extern uint32_t eraseErrors;
extern uint32_t eraseOK;
extern uint32_t writeCalls;
extern uint32_t maxWriteAddr;
extern uint32_t writeErrors;
extern uint32_t writeOK;
extern uint32_t writeERR;
#endif

static uint8_t commandArray[BOOT_CONFIG_MAX_PACKET_SIZE];

enum BOOT_COMMAND_RESPONSES
{
    COMMAND_SUCCESS = 1,
    UNSUPPORTED_COMMAND = 0xFF,
    BAD_ADDRESS = 0xFE,
    BAD_LENGTH  = 0xFD,
};

enum BOOT_COMMAND
{
    READ_VERSION = 0,
    READ_FLASH = 1,
    WRITE_FLASH = 2,
    ERASE_FLASH = 3,
    CALC_CHECKSUM = 8,
    RESET_DEVICE = 9
};


/******************************************************************************/
/* Private Function Prototypes                                                */
/******************************************************************************/

static enum BOOT_COMMAND_RESULT CommandError(enum BOOT_COMMAND_RESPONSES);
static enum BOOT_COMMAND_RESULT ReadVersion(void);

static void ResetDevice(void);
static bool IsLegalAddress(uint32_t addressToCheck);
static enum BOOT_COMMAND_RESULT EraseFlash(void);
static enum BOOT_COMMAND_RESULT WriteFlash(void);
static enum BOOT_COMMAND_RESULT ReadFlash(void);

/******************************************************************************/
/* Public Functions                                                           */
/******************************************************************************/
void BOOT_Initialize() 
{

}


enum BOOT_COMMAND_RESULT BOOT_ProcessCommand(void)
{    
    uint16_t bytes_ready = BOOT_COM_GetBytesReady();
    uint8_t command;
    uint16_t command_length;

    if (bytes_ready == 0) 
    { 
        return BOOT_COMMAND_NONE; 
    }
    if (bytes_ready < sizeof(struct CMD_STRUCT_0)) 
    { 
        return BOOT_COMMAND_INCOMPLETE; 
    }
    
    command = BOOT_COM_Peek(0);

    // validate the length of the command will not exceed the buffer size
    command_length = BOOT_COM_Peek(1) + BOOT_COM_Peek(2)*256 + sizeof(struct CMD_STRUCT_0);
    if ( ( command_length > BOOT_CONFIG_MAX_PACKET_SIZE ) && ( command != ERASE_FLASH ) )
    {
        return CommandError(BAD_LENGTH);
    }

    switch (command)
    {
        
    case WRITE_FLASH:
        return WriteFlash();

    case READ_FLASH:
        return ReadFlash();

    case READ_VERSION:
        return ReadVersion();

    case ERASE_FLASH:
        return EraseFlash();

    case RESET_DEVICE:
        ResetDevice();
        return BOOT_COMMAND_SUCCESS;

    default:
        return CommandError(UNSUPPORTED_COMMAND);
    }
    return BOOT_COMMAND_ERROR;
}


void BOOT_StartApplication()
{
    int (*user_application)(void);
    user_application = (int(*)(void))BOOT_CONFIG_APPLICATION_RESET_ADDRESS;
    user_application();
 
}  

/******************************************************************************/
/* Private Functions                                                          */
/******************************************************************************/

static enum BOOT_COMMAND_RESULT CommandError(enum BOOT_COMMAND_RESPONSES errorType)
{
    struct RESPONSE_TYPE_0 response;
    
    (void)BOOT_COM_Read(commandArray, sizeof(struct CMD_STRUCT_0));
    memcpy(&response, commandArray, sizeof(struct CMD_STRUCT_0));
    response.success = errorType;
    BOOT_COM_Write((uint8_t*) & response, sizeof (struct RESPONSE_TYPE_0 ) / sizeof (uint8_t));
    
    return BOOT_COMMAND_ERROR;
}

static enum BOOT_COMMAND_RESULT ReadVersion(void)
{
    struct GET_VERSION_RESPONSE response = {
        .cmd = 0,
        .dataLength = 0,
        .unlockSequence = 0,
        .address = 0,
        .version = BOOT_CONFIG_VERSION,
        .maxPacketLength = BOOT_CONFIG_MAX_PACKET_SIZE,
        .unused1 = 0,
        .deviceId = 0x3456,
        .unused2 = 0,
        .eraseSize = FLASH_ERASE_PAGE_SIZE_IN_INSTRUCTIONS * 2,
        .writeSize = MINIMUM_WRITE_BLOCK_SIZE,
        .unused3 = 0,
        .userRsvdStartSddress = 0,
        .userRsvdEndSddress = 0
    };
    
    (void)BOOT_COM_Read(commandArray, sizeof(struct CMD_STRUCT_0));
    BOOT_COM_Write((uint8_t*) & response, sizeof (struct GET_VERSION_RESPONSE ) / sizeof (uint8_t));

    return BOOT_COMMAND_SUCCESS;
}


static void Reset(void){
    asm ("reset");
}

static void ResetDevice(void)
{
    struct RESPONSE_TYPE_0 response = {
        .cmd = 9,
        .dataLength = 0,
        .unlockSequence = 0,
        .address = 0,

        .success = COMMAND_SUCCESS
    };         
    
    (void)BOOT_COM_Read(commandArray, sizeof(struct CMD_STRUCT_0));

    BOOT_COM_Write((uint8_t*) & response, sizeof (struct RESPONSE_TYPE_0) / sizeof (uint8_t));
    
    Reset();
 }


static bool IsLegalAddress(uint32_t addressToCheck)
{
   return ( (addressToCheck >= BOOT_CONFIG_PROGRAMMABLE_ADDRESS_LOW) && (addressToCheck < BOOT_CONFIG_PROGRAMMABLE_ADDRESS_HIGH) );
}


static enum BOOT_COMMAND_RESULT EraseFlash(void)
{
    bool goodErase= true;
    uint32_t eraseAddress;
    struct RESPONSE_TYPE_0 response;
    struct CMD_STRUCT_0 *pcommand = (struct CMD_STRUCT_0*) commandArray; 
    
    (void)BOOT_COM_Read(commandArray, sizeof(struct CMD_STRUCT_0));
            
    memcpy(&response, commandArray, sizeof(struct CMD_STRUCT_0));

    eraseAddress = pcommand->address;
    
#if __DEBUG
    // update monitor variables
    eraseCalls++;
    eraseCmdAddr=pcommand->address;
    eraseCmdDataLen=pcommand->dataLength;
    eraseLimit=( pcommand->address + FLASH_ERASE_RANGE_CAST pcommand->dataLength * FLASH_ERASE_PAGE_SIZE_IN_PC_UNITS);
#endif
    
     // check to make sure page is aligned here.
    if ( (eraseAddress & FLASH_ERASE_PAGE_MASK) != eraseAddress)
    {
        goodErase = false;
#if __DEBUG
        eraseErrors|=0x1;
#endif
    }
    
    FLASH_Unlock(pcommand->unlockSequence);

    while (goodErase && (eraseAddress < (pcommand->address + ( FLASH_ERASE_RANGE_CAST pcommand->dataLength * FLASH_ERASE_PAGE_SIZE_IN_PC_UNITS))))
    {
        if ( IsLegalAddress(eraseAddress) && IsLegalAddress(eraseAddress+FLASH_ERASE_PAGE_SIZE_IN_PC_UNITS-1) )
        {
            goodErase = (uint8_t) FLASH_ErasePage(eraseAddress);

            eraseAddress += FLASH_ERASE_PAGE_SIZE_IN_PC_UNITS;
#if __DEBUG
            // update max erase address
           if(eraseAddress>maxEraseAddr)maxEraseAddr=eraseAddress;
#endif
        }
        else
        {
            goodErase = false;
#if __DEBUG
            eraseErrors|=0x2;
#endif
        }
    }
 
    FLASH_Lock();

    if ((goodErase) && (eraseAddress == (pcommand->address + FLASH_ERASE_RANGE_CAST pcommand->dataLength * FLASH_ERASE_PAGE_SIZE_IN_PC_UNITS)))
    {
        response.success = COMMAND_SUCCESS;
    } 
    else
    {
        response.success = BAD_ADDRESS;
#if __DEBUG
        eraseErrors|=0x4;
#endif
   }
    
    BOOT_COM_Write((uint8_t*) & response, sizeof (struct RESPONSE_TYPE_0) / sizeof (uint8_t));

    /* destroy the unlock key so it isn't sitting around in memory. */
    pcommand->unlockSequence = ~FLASH_UNLOCK_KEY;
    
    if(response.success == COMMAND_SUCCESS)
    {
#if __DEBUG
        // update exit status (should match call count)
        eraseOK++;
#endif
       return BOOT_COMMAND_SUCCESS;
     }
    
    return BOOT_COMMAND_ERROR;
}

static enum BOOT_COMMAND_RESULT WriteFlash(void)
{
    struct CMD_STRUCT_0_WITH_PAYLOAD *pCommand = (struct CMD_STRUCT_0_WITH_PAYLOAD*) commandArray;
    struct RESPONSE_TYPE_0 response;
    
#if __DEBUG
    // update monitor variables
    writeCalls++;
#endif

    uint32_t flashAddress;   
    uint16_t dataLength, count;
    
    dataLength = BOOT_COM_Peek(2)<<8;
    dataLength |= BOOT_COM_Peek(1);
    
    if (BOOT_COM_GetBytesReady() < (sizeof(struct CMD_STRUCT_0) + dataLength))
    { 
#if __DEBUG
    writeErrors|=0x1;
#endif
        return BOOT_COMMAND_INCOMPLETE; 
    }  
    
    (void)BOOT_COM_Read(commandArray, sizeof(struct CMD_STRUCT_0) + dataLength);

    memcpy(&response, commandArray, sizeof(struct CMD_STRUCT_0));
    
    response.success = COMMAND_SUCCESS;

    flashAddress = pCommand->address;
    
    if ( IsLegalAddress(flashAddress) && IsLegalAddress(flashAddress+dataLength-1) &&
            ((dataLength % MINIMUM_WRITE_BLOCK_SIZE) == 0) && 
            (dataLength <= (BOOT_CONFIG_MAX_PACKET_SIZE - sizeof(struct CMD_STRUCT_0))) )
    {
        FLASH_Unlock(pCommand->unlockSequence);

        for (count = 0; count < dataLength; count += 8)
        {
            uint32_t flashData[2];

            memcpy(&flashData[0], &pCommand->data[count], 8);

#if __DEBUG
    // update max flash address
    if(pCommand->address+count/2 > maxWriteAddr)maxWriteAddr=pCommand->address+count/2;
#endif
            if (FLASH_WriteDoubleWord24(pCommand->address+count/2, flashData[0],flashData[1] ) == false)
            {
#if __DEBUG
    writeErrors|=0x2;
#endif
               response.success = BAD_ADDRESS-1;
                break;
            }
        }

        FLASH_Lock();
    }   
    else
    {
#if __DEBUG
    writeErrors|=0x4;
#endif
        response.success = BAD_ADDRESS;
    }
    
    BOOT_COM_Write((uint8_t*) & response, sizeof (struct RESPONSE_TYPE_0) / sizeof (uint8_t));
    
    /* destroy the unlock key so it isn't sitting around in memory. */
    pCommand->unlockSequence = ~FLASH_UNLOCK_KEY;
    
    if(response.success == COMMAND_SUCCESS){
#if __DEBUG
    // update exit status (should match call count)
    writeOK++;
#endif
        return BOOT_COMMAND_SUCCESS;
    }
    
    return BOOT_COMMAND_ERROR;
}
static enum BOOT_COMMAND_RESULT ReadFlash(void)
{
    struct CMD_STRUCT_0_WITH_PAYLOAD * const pCommand = (struct CMD_STRUCT_0_WITH_PAYLOAD*) commandArray;
    struct RESPONSE_TYPE_0_WITH_PAYLOAD response;
    uint32_t flashData;

    uint16_t count;
    
   (void)BOOT_COM_Read(commandArray, sizeof(struct CMD_STRUCT_0) );

    memcpy(&response, commandArray, sizeof(struct CMD_STRUCT_0));
    
    if ( IsLegalAddress(pCommand->address) && IsLegalAddress(pCommand->address+pCommand->dataLength-1) && 
            (pCommand->dataLength <= (BOOT_CONFIG_MAX_PACKET_SIZE - sizeof(struct CMD_STRUCT_0) - 1 )) && 
            ((pCommand->dataLength%4)==0) )
    {
        
        for (count = 0; count < pCommand->dataLength; count += 4)
        {
            flashData = FLASH_ReadWord24(pCommand->address+count/2);
            memcpy(&response.data[count], &flashData, 4);
        }

        response.success = COMMAND_SUCCESS;
    } 
    else
    {
        response.success = BAD_ADDRESS;
        response.dataLength = 0;        
    }
    
    BOOT_COM_Write((uint8_t*) & response, (sizeof (struct RESPONSE_TYPE_0) / sizeof (uint8_t)) + response.dataLength  );

    if(response.success == COMMAND_SUCCESS)
    {
        return BOOT_COMMAND_SUCCESS;
    }
    
    return BOOT_COMMAND_ERROR;
}


