openblt/Host/Source/SerialBoot/srecord.c

449 lines
18 KiB
C

/************************************************************************************//**
* \file srecord.c
* \brief Motorola S-record library header file.
* \ingroup SerialBoot
* \internal
*----------------------------------------------------------------------------------------
* C O P Y R I G H T
*----------------------------------------------------------------------------------------
* Copyright (c) 2014 by Feaser http://www.feaser.com All rights reserved
*
*----------------------------------------------------------------------------------------
* L I C E N S E
*----------------------------------------------------------------------------------------
* This file is part of OpenBLT. OpenBLT 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 3 of the License, or (at your option) any later
* version.
*
* OpenBLT 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.
*
* You have received a copy of the GNU General Public License along with OpenBLT. It
* should be located in ".\Doc\license.html". If not, contact Feaser to obtain a copy.
*
* \endinternal
****************************************************************************************/
/****************************************************************************************
* Include files
****************************************************************************************/
#include <assert.h> /* assertion module */
#include <sb_types.h> /* C types */
#include <string.h> /* for strcpy etc. */
#include <ctype.h> /* for toupper() etc. */
#include "srecord.h" /* S-record library */
/****************************************************************************************
* Type definitions
****************************************************************************************/
/** \brief Enumeration for the different S-record line types. */
typedef enum
{
LINE_TYPE_S1, /**< 16-bit address line */
LINE_TYPE_S2, /**< 24-bit address line */
LINE_TYPE_S3, /**< 32-bit address line */
LINE_TYPE_UNSUPPORTED /**< unsupported line */
} tSrecordLineType;
/****************************************************************************************
* Function prototypes
****************************************************************************************/
static tSrecordLineType SrecordGetLineType(const sb_char *line);
static sb_uint8 SrecordVerifyChecksum(const sb_char *line);
static sb_uint8 SrecordHexStringToByte(const sb_char *hexstring);
static sb_uint8 SrecordReadLine(sb_file srecordHandle, sb_char *line);
/************************************************************************************//**
** \brief Checks if the specified srecordFile exists and contains s-records.
** \param srecordFile The S-record file with full path if applicable.
** \return SB_TRUE on the S-record is valid, SB_FALSE otherwise.
**
****************************************************************************************/
sb_uint8 SrecordIsValid(const sb_char *srecordFile)
{
sb_file tempHandle;
sb_char line[SRECORD_MAX_CHARS_PER_LINE];
/* attempt to open the file */
tempHandle = SrecordOpen(srecordFile);
/* is the file available? */
if (tempHandle == SB_NULL)
{
/* cannot open the file */
return SB_FALSE;
}
/* all lines should be formatted as S-records. read the first one to check this */
if (SrecordReadLine(tempHandle, line) == SB_FALSE)
{
/* could not read a line. file must be empty */
SrecordClose(tempHandle);
return SB_FALSE;
}
/* check if the line starts with the 'S' character, followed by a digit */
if ( (toupper(line[0]) != 'S') || (isdigit(line[1]) == 0) )
{
SrecordClose(tempHandle);
return SB_FALSE;
}
/* still here so it is a valid s-record */
SrecordClose(tempHandle);
return SB_TRUE;
} /*** end of SrecordIsValid ***/
/************************************************************************************//**
** \brief Opens the S-record file for reading.
** \param srecordFile The S-record file with full path if applicable.
** \return The filehandle if successful, SB_NULL otherwise.
**
****************************************************************************************/
sb_file SrecordOpen(const sb_char *srecordFile)
{
/* open the file for reading */
return fopen(srecordFile, "r");
} /*** end of SrecordOpen ***/
/************************************************************************************//**
** \brief Parse the S-record file to obtain information about its contents.
** \param srecordHandle The S-record file handle. It is returned by SrecordOpen.
** \param parseResults Pointer to where the parse results should be stored.
** \return none.
**
****************************************************************************************/
void SrecordParse(sb_file srecordHandle, tSrecordParseResults *parseResults)
{
tSrecordLineParseResults lineResults;
/* start at the beginning of the file */
rewind(srecordHandle);
/* init data structure */
parseResults->address_high = 0;
parseResults->address_low = 0xffffffff;
parseResults->data_bytes_total = 0;
/* loop through all S-records with program data */
while (SrecordParseNextDataLine(srecordHandle, &lineResults) == SB_TRUE)
{
/* update byte total */
parseResults->data_bytes_total += lineResults.length;
/* is this a new lowest address? */
if (lineResults.address < parseResults->address_low)
{
parseResults->address_low = lineResults.address;
}
/* is this a new highest address? */
if ((lineResults.address + lineResults.length - 1) > parseResults->address_high)
{
parseResults->address_high = (lineResults.address + lineResults.length - 1);
}
}
/* reset to the beginning of the file again */
rewind(srecordHandle);
} /*** end of SrecordParse ***/
/************************************************************************************//**
** \brief Closes the S-record file.
** \param srecordHandle The S-record file handle. It is returned by SrecordOpen.
** \return none.
**
****************************************************************************************/
void SrecordClose(sb_file srecordHandle)
{
/* close the file handle if valid */
if (srecordHandle != SB_NULL)
{
fclose(srecordHandle);
}
} /*** end of SrecordClose ***/
/************************************************************************************//**
** \brief Reads the next S-record with program data, parses it and returns the
** results.
** \param srecordHandle The S-record file handle. It is returned by SrecordOpen.
** \param parseResults Pointer to where the parse results should be stored.
** \return SB_TRUE is valid parse results were stored. SB_FALSE in case of end-of-
** file.
**
****************************************************************************************/
sb_uint8 SrecordParseNextDataLine(sb_file srecordHandle, tSrecordLineParseResults *parseResults)
{
sb_char line[SRECORD_MAX_CHARS_PER_LINE];
sb_uint8 data_line_found = SB_FALSE;
tSrecordLineType lineType;
sb_uint16 bytes_on_line;
sb_uint16 i;
sb_char *linePtr;
/* first set the length paramter to 0 */
parseResults->length = 0;
while (data_line_found == SB_FALSE)
{
/* read the next line from the file */
if (SrecordReadLine(srecordHandle, line) == SB_FALSE)
{
/* end-of-file encountered */
return SB_FALSE;
}
/* we now have a line. check if it is a S-record data line */
lineType = SrecordGetLineType(line);
if (lineType != LINE_TYPE_UNSUPPORTED)
{
/* check if the checksum on the line is correct */
if (SrecordVerifyChecksum(line) == SB_TRUE)
{
/* found a valid line that can be parsed. loop will stop */
data_line_found = SB_TRUE;
break;
}
}
}
/* still here so we have a valid S-record data line. start parsing */
linePtr = &line[0];
/* all good so far, now read out the address and databytes for the line */
switch (lineType)
{
/* ---------------------------- S1 line type ------------------------------------- */
case LINE_TYPE_S1:
/* adjust pointer to point to byte count value */
linePtr += 2;
/* read out the number of byte values that follow on the line */
bytes_on_line = SrecordHexStringToByte(linePtr);
/* read out the 16-bit address */
linePtr += 2;
parseResults->address = SrecordHexStringToByte(linePtr) << 8;
linePtr += 2;
parseResults->address += SrecordHexStringToByte(linePtr);
/* adjust pointer to point to the first data byte after the address */
linePtr += 2;
/* determine how many data bytes are on the line */
parseResults->length = bytes_on_line - 3; /* -2 bytes address, -1 byte checksum */
/* read and store data bytes if requested */
for (i=0; i<parseResults->length; i++)
{
parseResults->data[i] = SrecordHexStringToByte(linePtr);
linePtr += 2;
}
break;
/* ---------------------------- S2 line type ------------------------------------- */
case LINE_TYPE_S2:
/* adjust pointer to point to byte count value */
linePtr += 2;
/* read out the number of byte values that follow on the line */
bytes_on_line = SrecordHexStringToByte(linePtr);
/* read out the 32-bit address */
linePtr += 2;
parseResults->address = SrecordHexStringToByte(linePtr) << 16;
linePtr += 2;
parseResults->address += SrecordHexStringToByte(linePtr) << 8;
linePtr += 2;
parseResults->address += SrecordHexStringToByte(linePtr);
/* adjust pointer to point to the first data byte after the address */
linePtr += 2;
/* determine how many data bytes are on the line */
parseResults->length = bytes_on_line - 4; /* -3 bytes address, -1 byte checksum */
/* read and store data bytes if requested */
for (i=0; i<parseResults->length; i++)
{
parseResults->data[i] = SrecordHexStringToByte(linePtr);
linePtr += 2;
}
break;
/* ---------------------------- S3 line type ------------------------------------- */
case LINE_TYPE_S3:
/* adjust pointer to point to byte count value */
linePtr += 2;
/* read out the number of byte values that follow on the line */
bytes_on_line = SrecordHexStringToByte(linePtr);
/* read out the 32-bit address */
linePtr += 2;
parseResults->address = SrecordHexStringToByte(linePtr) << 24;
linePtr += 2;
parseResults->address += SrecordHexStringToByte(linePtr) << 16;
linePtr += 2;
parseResults->address += SrecordHexStringToByte(linePtr) << 8;
linePtr += 2;
parseResults->address += SrecordHexStringToByte(linePtr);
/* adjust pointer to point to the first data byte after the address */
linePtr += 2;
/* determine how many data bytes are on the line */
parseResults->length = bytes_on_line - 5; /* -4 bytes address, -1 byte checksum */
/* read and store data bytes if requested */
for (i=0; i<parseResults->length; i++)
{
parseResults->data[i] = SrecordHexStringToByte(linePtr);
linePtr += 2;
}
break;
default:
/* will not happen */
break;
}
/* parsing all done */
return SB_TRUE;
} /*** end of SrecordParseNextDataLine ***/
/************************************************************************************//**
** \brief Inspects a line from a Motorola S-Record file to determine its type.
** \param line A line from the S-Record.
** \return the S-Record line type.
**
****************************************************************************************/
static tSrecordLineType SrecordGetLineType(const sb_char *line)
{
/* check if the line starts with the 'S' character, followed by a digit */
if ( (toupper(line[0]) != 'S') || (isdigit(line[1]) == 0) )
{
/* not a valid S-Record line type */
return LINE_TYPE_UNSUPPORTED;
}
/* determine the line type */
if (line[1] == '1')
{
return LINE_TYPE_S1;
}
if (line[1] == '2')
{
return LINE_TYPE_S2;
}
if (line[1] == '3')
{
return LINE_TYPE_S3;
}
/* still here so not a supported line type found */
return LINE_TYPE_UNSUPPORTED;
} /*** end of SrecordGetLineType ***/
/************************************************************************************//**
** \brief Inspects an S1, S2 or S3 line from a Motorola S-Record file to
** determine if the checksum at the end is corrrect.
** \param line An S1, S2 or S3 line from the S-Record.
** \return SB_TRUE if the checksum is correct, SB_FALSE otherwise.
**
****************************************************************************************/
static sb_uint8 SrecordVerifyChecksum(const sb_char *line)
{
sb_uint16 bytes_on_line;
sb_uint8 checksum = 0;
/* adjust pointer to point to byte count value */
line += 2;
/* read out the number of byte values that follow on the line */
bytes_on_line = SrecordHexStringToByte(line);
/* byte count is part of checksum */
checksum += bytes_on_line;
/* adjust pointer to the first byte of the address */
line += 2;
/* add byte values of address and data, but not the final checksum */
do
{
/* add the next byte value to the checksum */
checksum += SrecordHexStringToByte(line);
/* update counter */
bytes_on_line--;
/* point to next hex string in the line */
line += 2;
}
while (bytes_on_line > 1);
/* the checksum is calculated by summing up the values of the byte count, address and
* databytes and then taking the 1-complement of the sum's least signigicant byte */
checksum = ~checksum;
/* finally verify the calculated checksum with the one at the end of the line */
if (checksum != SrecordHexStringToByte(line))
{
/* checksum incorrect */
return SB_FALSE;
}
/* still here so the checksum was correct */
return SB_TRUE;
} /*** end of SrecordVerifyChecksum ***/
/************************************************************************************//**
** \brief Helper function to convert a sequence of 2 characters that represent
** a hexadecimal value to the actual byte value.
** Example: SrecordHexStringToByte("2f") --> returns 47.
** \param hexstring String beginning with 2 characters that represent a hexa-
** decimal value.
** \return The resulting byte value.
**
****************************************************************************************/
static sb_uint8 SrecordHexStringToByte(const sb_char *hexstring)
{
sb_uint8 result = 0;
sb_char c;
sb_uint8 counter;
/* a hexadecimal character is 2 characters long (i.e 0x4F minus the 0x part) */
for (counter=0; counter < 2; counter++)
{
/* read out the character */
c = toupper(hexstring[counter]);
/* check that the character is 0..9 or A..F */
if ( (c < '0') || (c > 'F') || ( (c > '9') && (c < 'A') ) )
{
/* character not valid */
return 0;
}
/* convert character to 4-bit value (check ASCII table for more info) */
c -= '0';
if (c > 9)
{
c -= 7;
}
/* add it to the result */
result = (result << 4) + c;
}
/* return the results */
return result;
} /*** end of SrecordHexStringToByte ***/
/************************************************************************************//**
** \brief Reads the next line from the S-record file handle.
** \param srecordHandle The S-record file handle. It is returned by SrecordOpen.
** \param line Destination buffer for the line characters. Should be of size
** SRECORD_MAX_CHARS_PER_LINE.
** \return SB_TRUE if successful, SB_FALSE otherwise.
**
****************************************************************************************/
static sb_uint8 SrecordReadLine(sb_file srecordHandle, sb_char *line)
{
/* init the line as an empty line */
line[0] = '\0';
/* loop as long as we find a non-empty line or end-of-file */
while (line[0] == '\0')
{
if (fgets(line, SRECORD_MAX_CHARS_PER_LINE, srecordHandle) == SB_NULL)
{
/* no more lines available */
return SB_FALSE;
}
/* replace the line termination with a string termination */
line[strcspn(line, "\n")] = '\0';
}
/* still here so not EOF and not and empty line, so success */
return SB_TRUE;
} /*** end of SrecordReadLine ***/
/*********************************** end of srecord.c **********************************/