openblt/Host/Source/LibOpenBLT/srecparser.c

801 lines
29 KiB
C

/************************************************************************************//**
* \file srecparser.c
* \brief Motorola S-record file parser source file.
* \ingroup Firmware
* \internal
*----------------------------------------------------------------------------------------
* C O P Y R I G H T
*----------------------------------------------------------------------------------------
* Copyright (c) 2017 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> /* for assertions */
#include <stdint.h> /* for standard integer types */
#include <stddef.h> /* for NULL declaration */
#include <stdbool.h> /* for boolean type */
#include <stdio.h> /* for standard I/O library */
#include <string.h> /* for string library */
#include <ctype.h> /* for toupper() etc. */
#include "util.h" /* Utility module */
#include "firmware.h" /* Firmware data module */
#include "srecparser.h" /* S-record parser */
/****************************************************************************************
* Type definitions
****************************************************************************************/
/** \brief Enumeration for the different supported S-record line types. */
typedef enum t_srec_parser_line_type
{
SREC_PARSER_LINE_TYPE_S0, /**< Header record. */
SREC_PARSER_LINE_TYPE_S1, /**< 16-bit address data record. */
SREC_PARSER_LINE_TYPE_S2, /**< 24-bit address data record. */
SREC_PARSER_LINE_TYPE_S3, /**< 32-bit address data record. */
SREC_PARSER_LINE_TYPE_S7, /**< 32-bit address termination. */
SREC_PARSER_LINE_TYPE_S8, /**< 24-bit address termination. */
SREC_PARSER_LINE_TYPE_S9, /**< 16-bit address termination. */
SREC_PARSER_LINE_TYPE_UNSUPPORTED /**< Unsupported line. */
} tSRecParserLineType;
/****************************************************************************************
* Function prototypes
****************************************************************************************/
static bool SRecParserLoadFromFile (char const * firmwareFile);
static bool SRecParserSaveToFile (char const * firmwareFile);
static bool SRecParserExtractLineData(char const * line, uint32_t * address,
uint32_t * len, uint8_t * data);
static tSRecParserLineType SRecParserGetLineType(char const * line);
static bool SRecParserVerifyChecksum(char const * line);
static bool SRecParserConstructLine(char * line, tSRecParserLineType lineType,
uint32_t address,
uint8_t const * data, uint8_t dataLen);
static uint8_t SRecParserHexStringToByte(char const * hexstring);
/****************************************************************************************
* Local constant declarations
****************************************************************************************/
/** \brief File parser structure filled with Motorola S-record parsing specifics. */
static const tFirmwareParser srecParser =
{
.LoadFromFile = SRecParserLoadFromFile,
.SaveToFile = SRecParserSaveToFile
};
/***********************************************************************************//**
** \brief Obtains a pointer to the parser structure, so that it can be linked to the
** firmware data module.
** \return Pointer to firmware parser structure.
**
****************************************************************************************/
tFirmwareParser const * SRecParserGetParser(void)
{
return &srecParser;
} /*** end of SRecParserGetParser ***/
/************************************************************************************//**
** \brief Parses the specified firmware file to extract firmware data and adds this
** data to the firmware data that is currently managed by the firmware data
** module.
** \param firmwareFile Filename of the firmware file to load.
** \return True if successful, false otherwise.
**
****************************************************************************************/
static bool SRecParserLoadFromFile (char const * firmwareFile)
{
bool result = false;
FILE *fp;
/* The bytes count entry on the S-record line is max 255 bytes. This include the
* address and the checksum. This would result in 255 * 2 = 510 characters. Another
* 4 characters are needed for the bytes count and line type characters. Then another
* two for possible line termination (new line + cariage return). This brings the total
* characters to 516. Note that this array was made static to lower the stack load.
*/
static char line[516];
/* The bytes count entry on the S-record line is max 255 bytes. This includes the
* address and checksum. This means the worst case max data bytes per line is 255 - 3.
* Note that this array was made static to lower the stack load.
*/
static uint8_t data[252];
uint32_t address;
uint32_t len;
/* Check parameters. */
assert(firmwareFile != NULL);
/* Only continue if the parameters are valid. */
if (firmwareFile != NULL) /*lint !e774 */
{
/* Open the file for reading. */
fp = fopen(firmwareFile, "r");
/* Only continue if the filepointer is valid. */
if (fp != NULL)
{
/* Start at the beginning of the file. */
rewind(fp);
/* Assume that everyting goes okay and then only set a negative result value upon
* detection of a problem.
*/
result = true;
/* Read the entire file, one line at a time. */
while (fgets(line, sizeof(line)/sizeof(line[0]), fp) != NULL )
{
/* Replace the line termination with a string termination. */
line[strcspn(line, "\n\r")] = '\0';
/* Extra data from the S-record line. */
if (SRecParserExtractLineData(line, &address, &len, data))
{
/* Only add data if there is actually something to add. */
if (len > 0)
{
/* Add the extracted data to the firmware data module. */
if (!FirmwareAddData(address, len, data))
{
/* Error detected. Flag it and abort. */
result = false;
break;
}
}
}
}
/* Close the file now that we are done with it. */
fclose(fp);
}
}
/* Give the result back to the caller. */
return result;
} /*** end of SRecParserLoadFromFile ***/
/************************************************************************************//**
** \brief Writes firmware data to the specified file in the correct file format.
** \param firmwareFile Filename of the firmware file to write to.
** \return True if successful, false otherwise.
**
****************************************************************************************/
static bool SRecParserSaveToFile (char const * firmwareFile)
{
bool result = false;
FILE *fp;
/* The bytes count entry on the S-record line is max 255 bytes. This include the
* address and the checksum. This would result in 255 * 2 = 510 characters. Another
* 4 characters are needed for the bytes count and line type characters. Then another
* two for possible line termination (new line + cariage return). This brings the total
* characters to 516. Note that this array was made static to lower the stack load.
*/
static char line[516];
/* The bytes count entry on the S-record line is max 255 bytes. This includes the
* address and checksum. This means the worst case max data bytes per line is 255 - 3.
* Note that this array was made static to lower the stack load.
*/
static uint8_t data[252];
tFirmwareSegment * segment;
uint32_t progDataLowestAddress = 0x00000000;
uint32_t progDataHighestAddress = 0xffffffff;
tSRecParserLineType dataLineType = SREC_PARSER_LINE_TYPE_S1;
tSRecParserLineType terminationLineType = SREC_PARSER_LINE_TYPE_S9;
uint32_t segmentIdx;
uint32_t currentAddress;
uint8_t currentByteCnt;
uint8_t const * currentDataPtr;
uint32_t segmentBytesLeft;
const uint8_t maxDataBytesPerLine = 32;
/* Check parameters. */
assert(firmwareFile != NULL);
/* Only continue if the parameters are valid and if there is something to save. */
if ( (firmwareFile != NULL) && (FirmwareGetSegmentCount() > 0) ) /*lint !e774 */
{
/* Open the file for writing. */
fp = fopen(firmwareFile, "w");
/* Only continue if the filepointer is valid. */
if (fp != NULL)
{
/* Init result value to okay at this point and only set it to error in case a
* problem was detected.
*/
result = true;
/* Determine the lowest memory address used in the program data. This address needs
* to be specified in the termination record at the end of the S-record file.
*/
segment = FirmwareGetSegment(0);
/* Sanity check. */
assert(segment != NULL);
if (segment == NULL) /*lint !e774 */
{
/* Flag error. */
result = false;
}
else
{
/* Store the lowest memory address. */
progDataLowestAddress = segment->base;
}
/* Determine the highest memory address used in the program data. This address
* is needed to determine the number of bits needed for the address in the
* S-record data lines (16, 24 or 32), which determines the type of S-record for
* the data lines (S1, S2, S3) and the termination line (S7, S8 or S9).
*/
if (result) /*lint !e774 */
{
segment = FirmwareGetSegment(FirmwareGetSegmentCount() - 1u);
/* Sanity check. */
assert(segment != NULL);
if (segment == NULL) /*lint !e774 */
{
/* Flag error. */
result = false;
}
else
{
progDataHighestAddress = segment->base + segment->length - 1u;
/* Does the address have more than 24 bits? */
if (progDataHighestAddress > 0xffffff)
{
dataLineType = SREC_PARSER_LINE_TYPE_S3;
terminationLineType = SREC_PARSER_LINE_TYPE_S7;
}
/* Does the address have more than 16 bits? */
else if (progDataHighestAddress > 0xffff)
{
dataLineType = SREC_PARSER_LINE_TYPE_S2;
terminationLineType = SREC_PARSER_LINE_TYPE_S8;
}
}
}
/* Extract just the filename and copy it to the data buffer. */
if (result) /*lint !e774 */
{
if (!UtilFileExtractFilename(firmwareFile, (char *)data))
{
/* Could not extract filename. */
result = false;
}
}
/* Construct and add the S0-record with the filename. */
if (result)
{
/* Construct the S0-record. */
result = SRecParserConstructLine(line, SREC_PARSER_LINE_TYPE_S0, 0x0000, data,
(uint8_t)strlen((char *)data));
if (result)
{
/* Add the S0-record. */
if (fprintf(fp, "%s\r\n", line) < 0)
{
/* Could not write line to the file. */
result = false;
}
}
}
/* Write all the program data records. Process one segment at a time. */
if (result)
{
/* Loop through all segments. */
for (segmentIdx = 0; segmentIdx < FirmwareGetSegmentCount(); segmentIdx++)
{
/* Obtain the segment. */
segment = FirmwareGetSegment(segmentIdx);
/* Sanity check. */
assert(segment != NULL);
if (segment == 0) /*lint !e774 */
{
/* Flag error and abort loop. */
result = false;
break;
}
/* Initialize base address, byte count and data pointer. */
currentAddress = segment->base;
currentDataPtr = segment->data;
segmentBytesLeft = segment->length;
/* Process al bytes in the segment. */
while (segmentBytesLeft > 0)
{
/* Determine the number of bytes that can be written. */
currentByteCnt = maxDataBytesPerLine;
if (segmentBytesLeft < maxDataBytesPerLine)
{
currentByteCnt = (uint8_t)segmentBytesLeft;
}
/* Construct the data record. */
result = SRecParserConstructLine(line, dataLineType, currentAddress,
currentDataPtr, currentByteCnt);
if (!result)
{
/* Error detected. No need to continue the loop. */
break;
}
/* Add the data record. */
if (fprintf(fp, "%s\r\n", line) < 0)
{
/* Could not write line to the file. Abort loop. */
result = false;
break;
}
/* Update loop variables. */
currentAddress += currentByteCnt;
currentDataPtr += currentByteCnt;
segmentBytesLeft -= currentByteCnt;
}
}
}
/* Write the termination record. */
if (result)
{
/* Construct the termination record. */
result = SRecParserConstructLine(line, terminationLineType,
progDataLowestAddress, data, 0u);
if (result)
{
/* Add the termination record. */
if (fprintf(fp, "%s\r\n", line) < 0)
{
/* Could not write line to the file. */
result = false;
}
}
}
}
}
/* Give the result back to the caller. */
return result;
} /*** end of SRecParserSaveToFile ***/
/************************************************************************************//**
** \brief Checks if the specified S-record line is of the type that contains program
** data. If it does, then the program data and base address are extracted and
** stored at the function parameter pointers.
** \param line Pointer to the line from an S-record file.
** \param address Pointer where the start address of the program data is stored.
** \param len Pointer for storing the number of extracted program data bytes.
** \param data Pointer to byte array where the extracted program data bytes are
** stored.
** \return True if successful, false otherwise.
**
****************************************************************************************/
static bool SRecParserExtractLineData(char const * line, uint32_t * address,
uint32_t * len, uint8_t * data)
{
bool result = false;
tSRecParserLineType lineType;
uint8_t dataByteCount = 0;
uint8_t bytesOnLine;
uint8_t idx;
/* Verify parameters. */
assert(line != NULL);
assert(address != NULL);
assert(len != NULL);
assert(data != NULL);
/* Only continue with valid parameters. */
if ( (line != NULL) && (address != NULL) && (len != NULL) &&
(data != NULL) ) /*lint !e774 */
{
/* Determine the line type. */
lineType = SRecParserGetLineType(line);
/* Only continue if the S-record line contains program data. */
if ( (lineType == SREC_PARSER_LINE_TYPE_S1) ||
(lineType == SREC_PARSER_LINE_TYPE_S2) ||
(lineType == SREC_PARSER_LINE_TYPE_S3) )
{
/* Verify the checksum of the line. */
if (SRecParserVerifyChecksum(line))
{
/* Adjust pointer to point to byte count value. */
line += 2u;
/* Read out the number of byte values that follow on the line. */
bytesOnLine = SRecParserHexStringToByte(line);
/* Process S1 line type. */
if (lineType == SREC_PARSER_LINE_TYPE_S1)
{
/* Read out the 16-bit address. */
line += 2u;
*address = (uint32_t)SRecParserHexStringToByte(line) << 8u;
line += 2u;
*address += (uint32_t)SRecParserHexStringToByte(line);
/* Determine how many data bytes are on the line. -2 bytes for address and
* -1 byte for the checksum.
*/
dataByteCount = bytesOnLine - 3u;
}
/* Process S2 line type. */
else if (lineType == SREC_PARSER_LINE_TYPE_S2)
{
/* Read out the 24-bit address. */
line += 2u;
*address = (uint32_t)SRecParserHexStringToByte(line) << 16u;
line += 2u;
*address += (uint32_t)SRecParserHexStringToByte(line) << 8u;
line += 2u;
*address += (uint32_t)SRecParserHexStringToByte(line);
/* Determine how many data bytes are on the line. -3 bytes for address and
* -1 byte for the checksum.
*/
dataByteCount = bytesOnLine - 4u;
}
/* Process S3 line type. */
else
{
/* Read out the 32-bit address. */
line += 2u;
*address = (uint32_t)SRecParserHexStringToByte(line) << 24u;
line += 2u;
*address += (uint32_t)SRecParserHexStringToByte(line) << 16u;
line += 2u;
*address += (uint32_t)SRecParserHexStringToByte(line) << 8u;
line += 2u;
*address += (uint32_t)SRecParserHexStringToByte(line);
/* Determine how many data bytes are on the line. -4 bytes for address and
* -1 byte for the checksum.
*/
dataByteCount = bytesOnLine - 5u;
}
/* Adjust pointer to point to the first data byte after the address. */
line += 2u;
/* Read and store data bytes. */
for (idx = 0; idx < dataByteCount; idx++)
{
data[idx] = SRecParserHexStringToByte(line);
line += 2u;
}
/* Store the number of data bytes that were copied to the array. */
*len = dataByteCount;
/* Data extraction complete. */
result = true;
}
}
}
/* Give the result back to the caller. */
return result;
} /*** end of SRecParserExtractLineData ***/
/************************************************************************************//**
** \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 tSRecParserLineType SRecParserGetLineType(char const * line)
{
tSRecParserLineType result = SREC_PARSER_LINE_TYPE_UNSUPPORTED;
/* Verify parameters. */
assert(line != NULL);
/* Only continue with valid parameters. */
if (line != NULL) /*lint !e774 */
{
/* Check if the line starts with the 'S' character, followed by a digit */
if ( (toupper((int32_t)(line[0])) == 'S') && (isdigit((int32_t)(line[1]))) )
{
/* Check the digit that follows the 'S' character. Currently only line types that
* contain program data are needed.
*/
switch (line[1])
{
case '0':
result = SREC_PARSER_LINE_TYPE_S0;
break;
case '1':
result = SREC_PARSER_LINE_TYPE_S1;
break;
case '2':
result = SREC_PARSER_LINE_TYPE_S2;
break;
case '3':
result = SREC_PARSER_LINE_TYPE_S3;
break;
case '7':
result = SREC_PARSER_LINE_TYPE_S7;
break;
case '8':
result = SREC_PARSER_LINE_TYPE_S8;
break;
case '9':
result = SREC_PARSER_LINE_TYPE_S9;
break;
default:
result = SREC_PARSER_LINE_TYPE_UNSUPPORTED;
break;
}
}
}
/* Give the result back to the caller. */
return result;
} /*** end of SRecParserGetLineType ***/
/************************************************************************************//**
** \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 True if the checksum is correct, false otherwise.
**
****************************************************************************************/
static bool SRecParserVerifyChecksum(char const * line)
{
bool result = false;
uint8_t bytes_on_line;
uint8_t checksum = 0;
/* Verify parameters. */
assert(line != NULL);
/* Only continue with with valid parameters. */
if (line != NULL) /*lint !e774 */
{
/* Adjust pointer to point to byte count value. */
line += 2u;
/* Read out the number of byte values that follow on the line. */
bytes_on_line = SRecParserHexStringToByte(line);
/* An S-record will always have at least a 16-bit address and a checksum value. */
if (bytes_on_line >= 3u)
{
/* Byte count is part of checksum. */
checksum += bytes_on_line;
/* Adjust pointer to the first byte of the address. */
line += 2u;
/* Add byte values of address and data, but not the final checksum. */
do
{
/* Add the next byte value to the checksum. */
checksum += SRecParserHexStringToByte(line);
/* Update counter. */
bytes_on_line--;
/* Point to next hex string in the line. */
line += 2u;
}
while (bytes_on_line > 1u);
/* 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 == SRecParserHexStringToByte(line))
{
/* Checksum correct. */
result = true;
}
}
}
/* Give the result back to the caller. */
return result;
} /*** end of SRecParserVerifyChecksum ***/
/************************************************************************************//**
** \brief Creates a NUL terminated S-record line, given the specified line type,
** address and data bytes. The checksum at the end of the line is also
** calculated and added.
** \param line Pointer to character array where the string will be stored.
** \param lineType The type of S-record line to construct.
** \param address The address to embed into the line after the byte count.
** \param data Point to byte array with data bytes to add to the line.
** \param dataLen The number of data bytes present in the data-array.
** \return True if successful, false otherwise.
**
****************************************************************************************/
static bool SRecParserConstructLine(char * line, tSRecParserLineType lineType,
uint32_t address, uint8_t const * data,
uint8_t dataLen)
{
bool result = false;
uint8_t addressBits;
uint8_t byteVal;
char hexByteStr[3];
uint8_t cnt;
uint8_t checksumVal = 0;
/* Verify parameters. */
assert(line != NULL);
if (dataLen > 0)
{
assert(data != NULL);
}
/* Only continue with valid parameters. */
if (line != NULL) /*lint !e774 */
{
/* Set the result value to successful and only change this upon detection of an
* error.
*/
result = true;
/* Start of with an empty string. */
line[0] = '\0';
/* Add the record type based on the lineType and store the number of bits in the
* address.
*/
switch (lineType)
{
case SREC_PARSER_LINE_TYPE_S0:
strcat(line, "S0");
addressBits = 16;
break;
case SREC_PARSER_LINE_TYPE_S1:
strcat(line, "S1");
addressBits = 16;
break;
case SREC_PARSER_LINE_TYPE_S2:
strcat(line, "S2");
addressBits = 24;
break;
case SREC_PARSER_LINE_TYPE_S3:
strcat(line, "S3");
addressBits = 32;
break;
case SREC_PARSER_LINE_TYPE_S7:
strcat(line, "S7");
addressBits = 32;
break;
case SREC_PARSER_LINE_TYPE_S8:
strcat(line, "S8");
addressBits = 24;
break;
case SREC_PARSER_LINE_TYPE_S9:
strcat(line, "S9");
addressBits = 16;
break;
case SREC_PARSER_LINE_TYPE_UNSUPPORTED:
default:
/* Invalid line type specified. Should not happed. */
addressBits = 16;
assert(false); /*lint !e506 */
break;
}
/* Next, add the number of bytes that will follow on the line. This is the size in
* bytes of the address, the actual number of data bytes and the checksum.
*/
byteVal = (addressBits / 8u) + dataLen + 1u;
checksumVal += byteVal;
if (sprintf(hexByteStr, "%02X", byteVal) != 2)
{
/* Error occurred. */
result = false;
}
else
{
/* Append it to the line. */
strcat(line, hexByteStr);
}
/* Add the address. */
for (cnt = (addressBits / 8u); cnt > 0; cnt--)
{
byteVal = (uint8_t)(address >> ( (cnt - 1u) * 8u));
checksumVal += byteVal;
if (sprintf(hexByteStr, "%02X", byteVal) != 2)
{
/* Error occurred. */
result = false;
}
else
{
/* Append it to the line. */
strcat(line, hexByteStr);
}
}
/* Add the data bytes. */
for (cnt = 0; cnt < dataLen; cnt++)
{
byteVal = 0;
if (data != NULL)
{
byteVal = data[cnt];
}
checksumVal += byteVal;
if (sprintf(hexByteStr, "%02X", byteVal) != 2)
{
/* Error occurred. */
result = false;
}
else
{
/* Append it to the line. */
strcat(line, hexByteStr);
}
}
/* Calculate and add the checksum. */
checksumVal = ~checksumVal;
byteVal = checksumVal;
if (sprintf(hexByteStr, "%02X", byteVal) != 2)
{
/* Error occurred. */
result = false;
}
else
{
/* Append it to the line. */
strcat(line, hexByteStr);
}
}
/* Give the result back to the caller. */
return result;
} /*** end of SRecParserConstructLine ***/
/************************************************************************************//**
** \brief Helper function to convert a sequence of 2 characters that represent
** a hexadecimal value to the actual byte value.
** Example: SRecParserHexStringToByte("2f") --> returns 47.
** \param hexstring String beginning with 2 characters that represent a hexa-
** decimal value.
** \return The resulting byte value.
**
****************************************************************************************/
static uint8_t SRecParserHexStringToByte(char const * hexstring)
{
uint8_t result = 0;
int32_t c;
uint8_t counter;
/* Verify parameters. */
assert(hexstring != NULL);
/* Only continue with valid parameters. */
if (hexstring != NULL) /*lint !e774 */
{
/* A hexadecimal character is 2 characters long (i.e 0x4F minus the 0x part). */
for (counter = 0; counter < 2u; counter++)
{
/* Read out the character */
c = toupper((int32_t)(hexstring[counter]));
/* Check that the character is 0..9 or A..F */
if (isxdigit(c))
{
/* 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 = (uint8_t)((result << 4u) + c);
}
}
}
/* Give the result back to the caller. */
return result;
} /*** end of SRecParserHexStringToByte ***/
/*********************************** end of srecparser.c *******************************/