/***********************************************************************
    Mark Butcher    Bsc (Hons) MPhil MIET

    M.J.Butcher Consulting
    Birchstrasse 20f,    CH-5406, Rütihof
    Switzerland

    www.uTasker.com    Skype: M_J_Butcher

    ---------------------------------------------------------------------
    File:        SDLoader.c
    Project:     uTasker SD card loader
    ---------------------------------------------------------------------
    Copyright (C) M.J.Butcher Consulting 2004..2013
    *********************************************************************
    03.03.2012 Add TWR_K53N512                                           {1}
    19.09.2012 Close Flash buffer when device has flash with row writing {2}
    25.11.2013 Limit wait when no SD card detected                       {3}

*/

/* =================================================================== */
/*                           include files                             */
/* =================================================================== */

#include "config.h"

#ifdef SDCARD_SUPPORT

/* =================================================================== */
/*                          local definitions                          */
/* =================================================================== */

#define OWN_TASK                   TASK_SD_LOADER

#define STATE_INIT                 0                                     // task states
#define STATE_ACTIVE               1
#define STATE_START_CHECKING       2
#define STATE_CHECKING             3
#define STATE_CHECK_SECRET_KEY     4
#define STATE_DELETING_FLASH       5
#define STATE_PROGRAMMING          6
#define STATE_VERIFYING            7


#define T_GO_TO_APP                1
#define T_CHECK_CARD               2

#define E_DO_NEXT                  1

#define CRC_BLOCK_SIZE             1024                                  // CRC check in internal flash made in blocks of this size
#define COPY_BUFFER_SIZE           256                                   // file processed in blocks of this size from SD card


/* =================================================================== */
/*                      local structure definitions                    */
/* =================================================================== */

typedef struct stUPLOAD_HEADER
{
    unsigned long  ulCodeLength;
    unsigned short usMagicNumber;
    unsigned short usCRC;
} UPLOAD_HEADER;

/* =================================================================== */
/*                 local function prototype declarations               */
/* =================================================================== */

static int fnUpdateSoftware(int iAppState, UTFILE *ptr_utFile, UPLOAD_HEADER *ptrFile_header);
static unsigned short fnCRC16(unsigned short usCRC, unsigned char *ptrInput, unsigned long ulBlockSize);
#if defined (_WINDOWS) || defined (_LITTLE_ENDIAN)
    static void fnHeaderToLittleEndian(UPLOAD_HEADER *file_header);
#endif
static void fnJumpToApplication(int iGo);                                // {3}

/* =================================================================== */
/*                             constants                               */
/* =================================================================== */

static const unsigned char ucSecretKey[] = _SECRET_KEY;

/* =================================================================== */
/*                     global variable definitions                     */
/* =================================================================== */


/* =================================================================== */
/*                      local variable definitions                     */
/* =================================================================== */

#if defined FLASH_ROW_SIZE && FLASH_ROW_SIZE > 0
    static unsigned char ucCodeStart[FLASH_ROW_SIZE];
#else
    static unsigned char ucCodeStart[4] = {0xff, 0xff, 0xff, 0xff};
#endif


// Application task
//
extern void fnSD_loader(TTASKTABLE *ptrTaskTable)
{
    static int iAppState = STATE_INIT;                                   // task state
    static unsigned char ucInputMessage[HEADER_LENGTH];                  // reserve space for receiving messages
    static UPLOAD_HEADER file_header;
    static UTDIRECTORY  *ptr_utDirectory = 0;                            // pointer to a directory object
    static UTFILE        utFile;                                         // local file object
    QUEUE_HANDLE         PortIDInternal = ptrTaskTable->TaskID;          // queue ID for task input

    if (STATE_INIT == iAppState) {
        iAppState = STATE_ACTIVE;
    #if defined FLASH_ROW_SIZE && FLASH_ROW_SIZE > 0
        uMemset(ucCodeStart, 0xff, sizeof(ucCodeStart));
    #endif
    #if defined KWIKSTIK || defined TWR_K40X256 || defined TWR_K53N512 && !defined SERIAL_INTERFACE && !defined USB_INTERFACE // {1}
        CONFIGURE_SLCD();
    #endif
        uTaskerMonoTimer(OWN_TASK, (DELAY_LIMIT)(SEC * 1), T_CHECK_CARD);// delay to allow SD card to be mounted
        ptr_utDirectory = utAllocateDirectory(DISK_D, 0);                // allocate a directory for use by this module associated with D:, without path name string length
    }
    else {
        while (fnRead(PortIDInternal, ucInputMessage, HEADER_LENGTH)) {  // check input queue
            if (ucInputMessage[MSG_SOURCE_TASK] == TIMER_EVENT) {        // timer event
                if (T_CHECK_CARD == ucInputMessage[MSG_TIMER_EVENT]) {   // check whether the SD card is ready
                    const UTDISK *ptrDiskInfo = fnGetDiskInfo(DISK_D);
                    if ((ptrDiskInfo->usDiskFlags == 0) || (ptrDiskInfo->usDiskFlags & DISK_NOT_PRESENT)) { // no card detected
                        _DISPLAY_SD_CARD_NOT_PRESENT();                  // optionally display that the card is not present
                        fnJumpToApplication(0);                          // {3} jump to application if retries expire (restart timer when not yet true)
                    }
                    else if (ptrDiskInfo->usDiskFlags & DISK_UNFORMATTED) {
                        _DISPLAY_SD_CARD_NOT_FORMATTED();                // optionally display that SD card is not formatted
                        fnJumpToApplication(0);                          // {3} jump to application if retries expire (restart timer when not yet true)
                    }
                    else {                        
                        utFile.ptr_utDirObject = ptr_utDirectory;
                        utFile.ownerTask = OWN_TASK;
                        _DISPLAY_SD_CARD_PRESENT();                      // optionally display that card is ready
                        if (utOpenFile(NEW_SOFTWARE_FILE, &utFile, UTFAT_OPEN_FOR_READ) == UTFAT_PATH_IS_FILE) {
                            utReadFile(&utFile, (unsigned char *)&file_header, sizeof(file_header)); // read the file header
    #if defined (_WINDOWS) || defined (_LITTLE_ENDIAN)
                            fnHeaderToLittleEndian(&file_header);
    #endif
                            if (utFile.ulFileSize == (file_header.ulCodeLength + sizeof(UPLOAD_HEADER))) { // content length matches
                                if ((file_header.usMagicNumber == VALID_VERSION_MAGIC_NUMBER)) { // check test that the header version (magic number) is correct
                                    iAppState = STATE_START_CHECKING;
                                    iAppState = fnUpdateSoftware(iAppState, &utFile, &file_header);
                                    return;
                                }
                                else {
                                    _DISPLAY_INVALID_CONTENT();          // optionally display that the software file has been found but has invalid content
                                }
                            }
                            else {
                                _DISPLAY_INVALID_CONTENT();              // optionally display that the software file has been found but has invalid content
                            }
                        }
                        else {
                            _DISPLAY_NO_FILE();                          // optionally display that the software file is not present on the SD card
                        }
                        uTaskerMonoTimer(OWN_TASK, (DELAY_LIMIT)(SEC * 1), T_GO_TO_APP); // {3} allow an existing application to start after short delay to display the state
                    }
                }
    #ifndef _WINDOWS
                else if (T_GO_TO_APP == ucInputMessage[MSG_TIMER_EVENT]) { // go to application
                    fnJumpToApplication(1);                              // {3} unconditional jump to application code
                }
    #endif
            }
            else {                                                       // interrupt event assumed
                iAppState = fnUpdateSoftware(iAppState, &utFile, &file_header);
                return;
            }
        }
    }
}

static void fnJumpToApplication(int iGo)                                 // {3}
{
    static int iMaxWait = 0;

    if ((iGo != 0) || (++iMaxWait >= MAX_WAIT_SD_CARD)) {                // if unconditional or if maximum attempts has been reached
#if !defined _WINDOWS
        if ((*(unsigned long *)_UTASKER_APP_START_ != 0xffffffff) && (*(unsigned long *)(_UTASKER_APP_START_ + 4) != 0xffffffff)) { // if an application is present
            uDisable_Interrupt();                                        // ensure interrupts are disabled when jumping to the application
            start_application(_UTASKER_APP_START_);                      // jump to application code
        }
#endif
    }
    uTaskerMonoTimer(OWN_TASK, (DELAY_LIMIT)(SEC * 1), T_CHECK_CARD);    // try again later
}

// SD card update state-event machine
//
static int fnUpdateSoftware(int iAppState, UTFILE *ptr_utFile, UPLOAD_HEADER *ptrFile_header)
{
    static unsigned short usCRC = 0;
    static int            iFlashMismatch = 0;
    static unsigned char  *ptrInternalFlash;
    static unsigned long  ulFileLength;
    int                   iNextState = iAppState;
    unsigned char         ucBuffer[COPY_BUFFER_SIZE];

    switch (iAppState) {
    case STATE_START_CHECKING:
        iFlashMismatch = 0;
        usCRC = 0;
        ptrInternalFlash = (unsigned char *)_UTASKER_APP_START_;
        iNextState = STATE_CHECKING;
        // Fall-through intentional
        //
    case STATE_CHECKING:                                                 // check the CRC of the file on the SD card
        utReadFile(ptr_utFile, ucBuffer, sizeof(ucBuffer));              // read a single buffer
        if (iFlashMismatch == 0) {                                       // if the code still matches 
            if (uMemcmp(fnGetFlashAdd(ptrInternalFlash), ucBuffer, ptr_utFile->usLastReadWriteLength) != 0) { // check whether the code is different
                iFlashMismatch = 1;                                      // the code is different so needs to be updated
            }
            else {
                ptrInternalFlash += ptr_utFile->usLastReadWriteLength;
            }
        }
        usCRC = fnCRC16(usCRC, ucBuffer, ptr_utFile->usLastReadWriteLength); // calculate the CRC of complete file content
        if (ptr_utFile->usLastReadWriteLength != sizeof(ucBuffer)) {     // end of file reached
            iNextState = STATE_CHECK_SECRET_KEY;
        }
        fnInterruptMessage(OWN_TASK, E_DO_NEXT);                         // schedule next
        break;

    case STATE_CHECK_SECRET_KEY:
        usCRC = fnCRC16(usCRC, (unsigned char *)ucSecretKey, sizeof(ucSecretKey)); // add the secret key
        if (usCRC == ptrFile_header->usCRC) {                            // content is valid
            _DISPLAY_VALID_CONTENT();                                    // optionally display that the content is not valid
            if (iFlashMismatch != 0) {                                   // valid new code which needs to be programmed
                fnEraseFlashSector((unsigned char *)UTASKER_APP_START, (UTASKER_APP_END - (unsigned char *)UTASKER_APP_START)); // delete application space
                iNextState = STATE_DELETING_FLASH;
                fnInterruptMessage(OWN_TASK, E_DO_NEXT);                 // schedule next
            }
            else {
                _DISPLAY_SW_OK();                                        // optionally display that the programmed application is already up-to-date
                uTaskerMonoTimer(OWN_TASK, (DELAY_LIMIT)(SEC * 1.5), T_GO_TO_APP); // start the application after a delay
                iNextState = STATE_ACTIVE;
            }
        }
        else {
            _DISPLAY_INVALID_CONTENT();                                  // optionally display that the content is not valid
            uTaskerMonoTimer(OWN_TASK, (DELAY_LIMIT)(SEC * 1), T_GO_TO_APP); // {3} start the existing application after a delay
            iNextState = STATE_ACTIVE;
        }
        break;

    case STATE_DELETING_FLASH:                                           // flash deleted
        ptrInternalFlash = (unsigned char *)_UTASKER_APP_START_;
        utSeek(ptr_utFile, sizeof(UPLOAD_HEADER), UTFAT_SEEK_SET);       // return to the start of the file after the header
        utReadFile(ptr_utFile, ucCodeStart, sizeof(ucCodeStart));        // store code start
        ptrInternalFlash += sizeof(ucCodeStart);
        iNextState = STATE_PROGRAMMING;
        // Fall-through intentional
        //
    case STATE_PROGRAMMING:                                              // programming from SD card file to internal application space
        utReadFile(ptr_utFile, ucBuffer, sizeof(ucBuffer));              // read buffer from file
        fnWriteBytesFlash(ptrInternalFlash, ucBuffer, ptr_utFile->usLastReadWriteLength); // program to Flash
        ptrInternalFlash += ptr_utFile->usLastReadWriteLength;
        if (ptr_utFile->usLastReadWriteLength != sizeof(ucBuffer)) {     // end of file reached
    #if defined FLASH_ROW_SIZE && FLASH_ROW_SIZE > 0                     // {2}
            fnWriteBytesFlash(ptrInternalFlash, 0, 0);                   // close any outstanding FLASH buffer from end of the file
    #endif
            ptrInternalFlash = (unsigned char *)_UTASKER_APP_START_;
            fnWriteBytesFlash(ptrInternalFlash, ucCodeStart, sizeof(ucCodeStart)); // program start of code to Flash
    #if defined FLASH_ROW_SIZE && FLASH_ROW_SIZE > 0                     // {2}
            fnWriteBytesFlash(ptrInternalFlash, 0, 0);                   // close any outstanding FLASH buffer from write to start of code
    #endif
    #ifdef USB_INTERFACE                                                 // the USB loader expects that the first long word in application flash to be non-blank so ensure it is the case
            fnWriteBytesFlash((unsigned char *)UTASKER_APP_START, ucCodeStart, sizeof(ucCodeStart));
        #if defined FLASH_ROW_SIZE && FLASH_ROW_SIZE > 0                 // {2}
            fnWriteBytesFlash((unsigned char *)UTASKER_APP_START, 0, 0); // close any outstanding FLASH buffer from previous write
        #endif
    #endif
            usCRC = 0;
            iNextState = STATE_VERIFYING;
            ulFileLength = (ptr_utFile->ulFileSize - sizeof(UPLOAD_HEADER));
        }
        fnInterruptMessage(OWN_TASK, E_DO_NEXT);                         // schedule next
        break;

    case STATE_VERIFYING:                                                // verify the CRC of the new application in flash
        if (ulFileLength >= CRC_BLOCK_SIZE) {
            usCRC = fnCRC16(usCRC, fnGetFlashAdd(ptrInternalFlash), CRC_BLOCK_SIZE);
            ulFileLength -= CRC_BLOCK_SIZE;
            ptrInternalFlash += CRC_BLOCK_SIZE;
            fnInterruptMessage(OWN_TASK, E_DO_NEXT);                     // schedule next
        }
        else {
            usCRC = fnCRC16(usCRC, fnGetFlashAdd(ptrInternalFlash), ulFileLength); // last block
            usCRC = fnCRC16(usCRC, (unsigned char *)ucSecretKey, sizeof(ucSecretKey)); // add the secret key
            if (usCRC == ptrFile_header->usCRC) {                        // new code has not been verified as being correct
                _DISPLAY_SW_OK();                                        // optionally display that the code was successfully programmed
                uTaskerMonoTimer(OWN_TASK, (DELAY_LIMIT)(SEC * 1.5), T_GO_TO_APP); // start the application after a short delay
            }
            else {
                _DISPLAY_ERROR();                                        // optionally display that there was an error with the image loaded to flash
                uTaskerMonoTimer(OWN_TASK, (DELAY_LIMIT)(SEC * 4), T_CHECK_CARD); // try again or allow application to start after a further check
            }
            iNextState = STATE_ACTIVE;
        }
        break;
    }
    return iNextState;
}

#if defined (_WINDOWS) || defined (_LITTLE_ENDIAN)
static void fnHeaderToLittleEndian(UPLOAD_HEADER *file_header)
{
    unsigned short usShort;
    unsigned long  ulLong;

    usShort = (file_header->usCRC >> 8);
    file_header->usCRC <<= 8;
    file_header->usCRC |= usShort;

    usShort = (file_header->usMagicNumber >> 8);
    file_header->usMagicNumber <<= 8;
    file_header->usMagicNumber |= usShort;

    ulLong = (file_header->ulCodeLength >> 24);
    ulLong |= (((file_header->ulCodeLength >> 16) & 0xff) << 8);
    ulLong |= (((file_header->ulCodeLength >> 8) & 0xff) << 16);
    ulLong |= (((file_header->ulCodeLength) & 0xff) << 24);
    file_header->ulCodeLength = ulLong;
}
#endif


// CRC-16 routine
//
static unsigned short fnCRC16(unsigned short usCRC, unsigned char *ptrInput, unsigned long ulBlockSize)
{
    while (ulBlockSize--) {
        usCRC = (unsigned char)(usCRC >> 8) | (usCRC << 8);
        usCRC ^= *ptrInput++;
        usCRC ^= (unsigned char)(usCRC & 0xff) >> 4;
        usCRC ^= (usCRC << 8) << 4;
        usCRC ^= ((usCRC & 0xff) << 4) << 1;
    }
    return usCRC;
}
#endif


