/*

Freely Distributable C30 Simulator Package

Copyright (c) 1996-1998 The University of Texas
All Rights Reserved.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
The GNU Public License is available in the file LICENSE, or you
can write to the Free Software Foundation, Inc., 59 Temple Place -
Suite 330, Boston, MA 02111-1307, USA, or you can find it on the
World Wide Web at http://www.fsf.org.

Authors: Chi Duong, Brian Evans, and Chris Moy
Version: @(#)loadc30.cc	1.28	01/19/98 

Department of Electrical and Computer Engineering
The University of Texas, Austin, TX 78712-1084

*/

#include <stdio.h>
#include <string.h>

#include "pipeline.h"
#include "state.h"
#include "memmap.h"
#include "simmisc.h"
#include "simulator.h"

/*
Read one string and make sure that it is null terminated
and remove the trailing newline character
*/
char* C30ReadStringFromStream(char* buffer, int len, FILE* streamptr) {
  buffer[0] = 0;
  if ( ! feof(streamptr) ) {
    int slen;
    fgets(buffer, len, streamptr);
    buffer[len - 1] = 0;
    slen = strlen(buffer);
    if ( buffer[--slen] == '\n' )
      buffer[slen] = 0;
  }
  return buffer;
}

/*
Check if we need to swap bytes as we read it the C30 program.
The PC orders bits from lowest to highest.  Sparc workstations
order bits from the highest to lowest.  We test for byte swapping
by storing an integer into a C30 word (an unsigned 32-bit location).
We then read the first byte of the C30 word.  The byte swapping
test is with respect to an IBM PC.
*/
static int byteSwappedvsPC() {
    uint32 c30word = 0x0102;
    unsigned char* highestByte = (unsigned char*)&c30word;
    return (*highestByte == 0);
}

/*
Perform byte swapping on a C30 word.  A C30 word is always
32 bits (4 bytes) long.
*/
static uint32 swapBytes(const uint32 word) {
    uint32 c30word;
    const unsigned char* wordBytePtr = (const unsigned char*)&word;
    unsigned char* c30wordBytePtr = (unsigned char*)&c30word;
    c30wordBytePtr[0] = wordBytePtr[3];
    c30wordBytePtr[1] = wordBytePtr[2];
    c30wordBytePtr[2] = wordBytePtr[1];
    c30wordBytePtr[3] = wordBytePtr[0];
    return c30word;
}

/*
This function reads one section and returns 1 upon loading successfully;
otherwise, it returns 0 to indicate error or invalidC30Memory is encountered
*/
inline static
int readOneSection(state* st, uint32 start, uint32 size, FILE* filePtr) {
    char input[C30SIM_MAX_BUFFER_LENGTH];
    uint32 wordsRead = 0;
    uint32 end = start + size - 1;
    uint32* addrPtr = C30SimMemMap(st, start);
    uint32* endSection = C30SimMemMap(st, end);

    /* Check for an invalid C30 memory address */
    if ( (addrPtr == &st->dummy) || (endSection == &st->dummy)) {
        /* ANSI C/C++ will concatenate strings separated by white space */
        fprintf(stderr,
                "Invalid C30 memory while loading addresses "
                C30_MEMORY_PRINTF
                " to "
                C30_MEMORY_PRINTF
                "\n",
                start, end);
        fflush(stderr);
        return FALSE;
    }

    while((! feof(filePtr)) && (wordsRead < size)) {
        C30ReadStringFromStream(input, C30SIM_MAX_BUFFER_LENGTH, filePtr);
        sscanf(input, C30_MEMORY_SCANF, addrPtr++);
        wordsRead++;
    }

    if ( wordsRead < size) {
        /* ANSI C/C++ will concatenate strings separated by white space */
        fprintf(stderr,
                "Inconsistency in section size."
                "The input file specifies "
                C30_UINT32_PRINTF_DECIMAL
                " words, but only has "
                C30_UINT32_PRINTF_DECIMAL
                " words\n",
                size, wordsRead);
        fflush(stderr);
        return FALSE;
    }

    return TRUE;
}

/*
Open a specified file and copies its contents to a specified c30
simulated address. The PC is also set to the beginning of the copy,
which may be incorrect setting if a program starts with a data
(variables) section.  If successful, this routine returns the number
of words read; otherwise, it returns 0 to indicate failure.
The function also sets the value of pcPtr to the actual value of the
PC to the value of addrPtr.
*/
uint32 C30SimLoadC30(state* st, const char* filename,
                     uint32* addrPtr, uint32* pcPtr)  {
    FILE* c30fp;
    int byteSwapFlag = byteSwappedvsPC();
    uint32 wordsRead = 0;
    uint32 beginning = *addrPtr;

    *pcPtr = beginning;
    if ((c30fp = fopen(filename, "r")) != NULL) {
        int invalidC30Memory = 0;
        while (! feof(c30fp)) {
            uint32* c30AddrPtr = C30SimMemMap(st, beginning);

            /* Check for an invalid C30 memory address */
            if ( ( invalidC30Memory = (c30AddrPtr == &st->dummy) ) ) break;

            /* Read one C30 word from the file, and bail out on an error */
            /* FIXME: We may need to swap bytes since the host machine
               and the C30 may not order bits in the same way */
            if ( (fread(c30AddrPtr, sizeof(uint32), 1, c30fp) != 1) &&
                 (! feof(c30fp)) ) {
                fprintf(stderr, "Error reading '%s' at word %ld\n",
                        filename, (long)wordsRead);
                fflush(stderr);
                break;
            }
            if (byteSwapFlag) {
                *c30AddrPtr = swapBytes(*c30AddrPtr);
            }

            /* Increment the C30 address and the number of C30 words read */
            beginning++;
            wordsRead++;
        }
        if (invalidC30Memory) {
            int fileLength = wordsRead;
            while (! feof(c30fp)) {
                uint32 temp;
                if ( fread(&temp, sizeof(uint32), 1, c30fp) != 1 ) break;
                fileLength++;
            }
            fprintf(stderr, "Only read %ld of %d words from the file '%s'\n.\n",
                    (long)wordsRead, fileLength, filename);
            fflush(stderr);
        }
        fclose(c30fp);
    }
    else {
        fprintf(stderr, "Cannot open '%s' for reading.\n", filename);
        fflush(stderr);
    }
    return wordsRead;
}

/*
Opens a specified .hex file and copies its contents to the address given
by beginning.  If beginning is 0, then the beginning address is read
as the defaultPC from the header of the .hex file.

Each .hex file starts with first 6-line header containing information for
bootloading.  The 5th line specifies defaultPC which is used by the
C30SimLoadHex to calculate the actualPC.  Follow the header is hex code
section(s).  Each section begins with 2 lines specifying the section size
and section default start address.  The function will calculate "offset"
base on the 1st default start address and "beginning".  All other section
default start address(es) (if any) will be adjusted to the actual section
start address using this "offset".  In default load case, "offset" will be
0, actual-section-start address(es) is the default address(es) specified
in .hex file.  Loading process will stop if section size is 0.

If the file is loaded successfully, this function returns the number of
words read; otherwise, it returns 0.  The function also sets the value of
pcPtr to the actual value of the PC, and the value of addrPtr to the
actual load address if it was not previous set.
*/
uint32 C30SimLoadHex(state* st, const char* filename,
                     uint32* addrPtr, uint32* pcPtr)  {
    uint32 wordsRead = 0;
    FILE* hexfp;
    uint32 beginning = *addrPtr;
    int getInfoFromFile = (*addrPtr == 0);

    *pcPtr = *addrPtr;
    if ((hexfp = fopen(filename, "r")) == NULL) {
        fprintf(stderr, "Cannot open '%s' for reading.\n", filename);
        fflush(stderr);
        return 0;
    }
    else {
        char input[C30SIM_MAX_BUFFER_LENGTH];
        long count = 0;
        long offset = 0;
        uint32 defaultPC = 0;
        uint32 sectionSize = 0;
        uint32 value = 0;

        /* process eight-line header of .hex file */
        while ((! feof(hexfp)) && (count++ < 8)) {
            C30ReadStringFromStream(input, C30SIM_MAX_BUFFER_LENGTH, hexfp);
            /* defaultPC is specified on the 5th line in hex file */
            if (count == 5) {
                if (sscanf(input, C30_MEMORY_SCANF, &value) != 1) break;
                defaultPC = value;
            }
            /* first section size in specified in the 7th line in hex file */
            if (count == 7) {
                if (sscanf(input, C30_MEMORY_SCANF, &sectionSize) != 1) break;
            }
            /* default load address is specified on the 8th line in hex file */
            if (count == 8) {
                uint32 actualPC = 0;
                if (sscanf(input, C30_MEMORY_SCANF, &value) != 1) break;
                /* default load: address not specified by the beginning
                   argument. Use values specified in .hex file */
                if (getInfoFromFile) {
                  beginning = value;
                  *addrPtr = value;
                }
                offset = value - beginning;      /* offset is 0 by default */
                actualPC = defaultPC - offset;
                *pcPtr = actualPC;
            }
        }

        if (count < 8) {
            fprintf(stderr,
                    "Error while reading the header of the hex file '%s'.\n",
                    filename);
            fflush(stderr);
        }
        /* process hex code of the .hex file */
        else if (readOneSection(st, beginning, sectionSize, hexfp)) {
            wordsRead = sectionSize;
            while (! feof(hexfp))  {
                C30ReadStringFromStream(input, C30SIM_MAX_BUFFER_LENGTH, hexfp);
                if (sscanf(input, C30_MEMORY_SCANF, &sectionSize) != 1) break;
                if (sectionSize == 0) break;

                C30ReadStringFromStream(input,C30SIM_MAX_BUFFER_LENGTH, hexfp);
                if (sscanf(input, C30_MEMORY_SCANF, &value) != 1) break;

                beginning = value - offset;
                if (!readOneSection(st, beginning, sectionSize, hexfp)) break;

                wordsRead += sectionSize;
            }
        }
    }
    fclose(hexfp);

    return wordsRead;
}
