#!/usr/bin/env python ''' /******************************************************************************* * DISCLAIMER * This software is supplied by Renesas Electronics Corporation and is only * intended for use with Renesas products. No other uses are authorized. This * software is owned by Renesas Electronics Corporation and is protected under * all applicable laws, including copyright laws. * THIS SOFTWARE IS PROVIDED "AS IS" AND RENESAS MAKES NO WARRANTIES REGARDING * THIS SOFTWARE, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING BUT NOT * LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * AND NON-INFRINGEMENT. ALL SUCH WARRANTIES ARE EXPRESSLY DISCLAIMED. * TO THE MAXIMUM EXTENT PERMITTED NOT PROHIBITED BY LAW, NEITHER RENESAS * ELECTRONICS CORPORATION NOR ANY OF ITS AFFILIATED COMPANIES SHALL BE LIABLE * FOR ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES FOR * ANY REASON RELATED TO THIS SOFTWARE, EVEN IF RENESAS OR ITS AFFILIATES HAVE * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * Renesas reserves the right, without notice, to make changes to this software * and to discontinue the availability of this software. By using this software, * you agree to the additional terms and conditions found by accessing the * following link: * http://www.renesas.com/disclaimer * * Copyright (C) 2013 Renesas Electronics Corporation. All rights reserved. *******************************************************************************/ /**************************************************************************** * File Name : FL_MOT_Converter.py * Description : This application takes an S-Record file and converts it into * a Load Image to be used with the Renesas FlashLoader project. * The code is setup so that if you are just wanting to change * the size of entries in a structure then the only thing * you need to change is the number associated with the * entry in the dictionaries below (FL_LI_FORMAT and * FL_BH_FORMAT). You can edit the image header valid_mask * value using the -m option. You can edit the block header * valid_mask value by changing FL_BH_VALID_MASK. If you * want to edit more than the size of entries then you will * need to dig in deeper. * * Notes : For seeing the format of a S-Record file I recommend looking * here : http://www.amelek.gda.pl/avr/uisp/srecord.htm ******************************************************************************/ /****************************************************************************** * History : MM.DD.YYYY Version Information * : 03.18.2010 Ver. 1.00 First Release - BCH * : 09.20.2010 Ver. 2.00 Cleaned up code, made easier to * modify - BCH * : 03.01.2012 Ver. 3.00 Added '-m' option to set the app's * valid mask value. The default value is * 0xAA. If the read value does not match * the expected value then an error * message is output. * : 08.08.2016 Ver. 4.00 Added -r option to set the flash size * that the ImageHeader->checksum_fw_image will use * to calculate the CRC. It is specified in * bytes. * Added the ImageBlock valid_mask, * block_identifier, flash_address, data_size * and next_block_address to data_checksum * : 09.27.2016 Ver. 5.00 Updates for Synergy, diverging... * ******************************************************************************/ ''' #Used for getting input arguments and exiting import sys #Used to split extension off of the input filename (that's it) import os #Used for string operations import string #binascii is used for converting from ASCII to binary import binascii #crcmod is used for doing the CRC16 import crcmod #Used for unpacking FL_CurAppHeader from S-Record file (like C style struct) from struct import * #This allows us to define the 'struct' that we use later from collections import namedtuple #This is the overall class that processes an S-Record file class FL_MOT_Converter: #Number of bytes used for checksum in S-Record file CHECKSUM_BYTES = 1 #If the user wants to change the size of the fields in the Load Image Header or Data Block #Header then they can do this below. There is a Python dictionary for each header below #with the name of the structure entry and the number of bytes associated with that entry. #If for instance you wanted to change the 'image_id' field to be 2 bytes you would change #the FL_LI_FORMAT definition below to 'image_id':2 #An array for holding Load Image size format #size_bch Size of the BCH file #size_raw Can span multiple memory instances. Should be the same size as the sum of memory instance slots used. #sf_firmware_image_file_header_t FL_LI_FORMAT = {'valid_mask':1, 'version_number':1, 'image_identifier':2, 'num_blocks':4, 'size_bch':4, 'size_raw':4, 'maximum_block_size':4, 'checksum_bch_file':4, 'checksum_fw_image':4, 'application_start_address':4, 'num_memory_instances':4, 'p_app_memory_instance':4, 'first_block_address':4, 'successfully_stored':4, 'checksum_bch_file_header':4 } #Number of bytes in Load File Header FL_LI_STRUCT_SIZE = sum([i for i in FL_LI_FORMAT.values()]) #Number of bytes to checksum_fw_image in header FL_LI_FW_CHKSM_OFFSET = FL_LI_FORMAT['valid_mask'] + FL_LI_FORMAT['version_number'] + FL_LI_FORMAT['image_identifier'] + FL_LI_FORMAT['num_blocks'] + FL_LI_FORMAT['size_bch'] + FL_LI_FORMAT['size_raw'] + FL_LI_FORMAT['maximum_block_size'] + FL_LI_FORMAT['checksum_bch_file'] #An array for holding Block Header size format #sf_firmware_image_bch_block_header_t FL_BH_FORMAT = {'valid_mask':1, 'block_identifier':2, 'flash_address':4, 'data_size':4, 'data_checksum':4, 'next_block_address':4, 'checksum_bch_block_header':4} #Number of bytes in Data Block Header FL_BH_STRUCT_SIZE = sum([i for i in FL_BH_FORMAT.values()]) #Other defines for Load Image Headers and Data Block Headers FL_BH_VALID_MASK = "AF" #Holds current sequence number for record block_identifier = 0 #Initializer function for this class. It takes in parameters #and initializes class variables. It also configures the CRC #calculator that will be used. def __init__(self, input_file, output_file, maximum_block_size, fill_space, header_loc, in_valid_mask, slot_size, application_start_address): self.mot_filename = input_file self.out_filename = output_file self.maximum_block_size = maximum_block_size self.max_data_size = maximum_block_size - self.FL_BH_STRUCT_SIZE self.max_fill_space = fill_space self.header_location = header_loc self.input_valid_mask = in_valid_mask self.header_bytes_left = self.FL_LI_STRUCT_SIZE self.slot_size = slot_size self.fileheader = "" self.filesize = 0 self.size_raw = 0 self.num_blocks = 0 #Used for CRC - CCITT - x^16 + x^12 + x^5 + 1 self.g16 = 0x11021 self.crc_init = 0xFFFF #CRC used for the entire file - Image CRC self.file_crc = crcmod.Crc(self.g16, self.crc_init, 0) #CRC used for each file header self.file_header_crc = crcmod.Crc(self.g16, self.crc_init, 0) #CRC used for selected flash memory range self.checksum_fw_image = 0 #Address of image in MCU flash, runnable location self.application_start_address = application_start_address self.CRCTable=SRecordCRC(application_start_address, slot_size, header_loc) #If an error is found in the S-Record file then this function is called def found_error(self): print "Each line in a S-Record should start with a 'S'" print "The file you input had a line that started without" print "an 'S'. Please check to make sure you have a valid" print "S-Record file." sys.exit() #This function packages up a Data Block and writes it to the output file def write_block(self,output_file, current_buffer, msb_start_address): #Print valid mask 0xAF write_str = binascii.unhexlify(self.switch_endian(self.FL_BH_VALID_MASK)) #Write Sequence ID msb_sequence_num = ("%0" + str(self.FL_BH_FORMAT['block_identifier']*2) + "x") % self.block_identifier #Switch to LSB lsb_sequence_num = self.switch_endian(msb_sequence_num) #Write 'block_identifier' to file write_str += binascii.unhexlify(lsb_sequence_num) #increment sequence number self.block_identifier += 1 #Print start address for block #Pad address if not correct number of chars msb_start_address = ("0"*((self.FL_BH_FORMAT['flash_address']*2) - len(msb_start_address))) + msb_start_address #Switch to LSB lsb_flash_address = self.switch_endian(msb_start_address) #Write 'flash_address' field to file write_str += binascii.unhexlify(lsb_flash_address) #Print size of data block in bytes msb_size_string = ("%0" + str(self.FL_BH_FORMAT['data_size']*2) + "x") % (len(current_buffer)/2) #Switch to LSB lsb_size_string = self.switch_endian(msb_size_string) #Write 'data_size' field to file write_str += binascii.unhexlify(lsb_size_string) #Print CRC of data - Using CCITT - x^16 + x^12 + x^5 + 1 block_data_crc = crcmod.Crc(self.g16, self.crc_init, 0) block_data_crc.update(binascii.unhexlify(current_buffer)) write_str += (binascii.unhexlify(self.switch_endian(("%0" + str(self.FL_BH_FORMAT['data_checksum']*2) + "x") % block_data_crc.crcValue))) block_crc = crcmod.Crc(self.g16, self.crc_init, 0) block_crc.update(write_str) block_crc_save = (binascii.unhexlify(self.switch_endian(("%0" + str(self.FL_BH_FORMAT['checksum_bch_block_header']*2) + "x") % block_crc.crcValue))) #Print empty record for MCU to fill in for 'next block header address' #The 'join' command below will join strings of FF together to make FFFF... #for however many bytes I need write_str += binascii.unhexlify(''.join(self.FL_BH_FORMAT['next_block_address']*['FF'])) #Write 'checksum_bch_block_header' to file write_str += block_crc_save #Print data write_str += binascii.unhexlify(current_buffer) #Update size_raw, this is also updated below for gaps between addresses self.size_raw += (len(current_buffer)/2) if(self.size_raw > self.slot_size): raise Exception("Slot size is smaller than the firmware image size") #Write new block to file output_file.write(write_str) #Update file CRC self.file_crc.update(write_str) #Update filesize self.filesize += (len(current_buffer)/2) + self.FL_BH_STRUCT_SIZE #Update num_blocks self.num_blocks += 1 #This function handles switching MSB to LSB and vice versa def switch_endian(self,temp): #Check to see if argument is 1 byte long, if so send it back if(len(temp) == 1): return temp #Check to make sure length is even if(len(temp)%2 == 1): print 'Switching endian failed. Input should always have even number length' sys.exit() #Switch endian temp_length = len(temp) #Do first iteration temp_length = temp_length - 2 switched = temp[temp_length:] while(temp_length > 0): #Append next byte switched = switched + temp[temp_length-2:temp_length] temp_length = temp_length - 2 return switched #This function runs crc over "erased flash" (0xFF) def run_erased_flash(self, range_for_flash_gap_crc): while(range_for_flash_gap_crc > 0): range_for_flash_gap_crc -= 1 #Update size_raw, this is also updated in write_block self.size_raw += 1 #This function is called to process the S-Record file def Process(self): #Open input file try: with open(self.mot_filename, "r") as mot_file: mot_lines=mot_file.readlines() except: print 'Error opening input file ' , self.mot_filename raise #Open a new file for output try: out_file = open(self.out_filename, "w+b") except: print 'Error opening output file ' , self.out_filename raise #Write as much of the load file header as we can. We'll #come back and write the rest at the end. #Print holders for Valid Mask, Image ID, Version #, Size of Load Image #Using .join() to make variable length string of all F's out_file.write(binascii.unhexlify(''.join((self.FL_LI_FORMAT['valid_mask'] + self.FL_LI_FORMAT['version_number'] + self.FL_LI_FORMAT['image_identifier'] + self.FL_LI_FORMAT['num_blocks'] + self.FL_LI_FORMAT['size_bch'] + self.FL_LI_FORMAT['size_raw']) *['FF']))) #Print max block header size msb_max_block_size = ("%0" + str(self.FL_LI_FORMAT['maximum_block_size']*2) + "x") % self.maximum_block_size #Switch to LSB lsb_max_block_size = self.switch_endian(msb_max_block_size) #Write out 'maximum_block_size' out_file.write(binascii.unhexlify(lsb_max_block_size)) #Print holders for Image CRC, Raw CRC, Address in Ext Memory, #and Successfully Stored. Using .join() again out_file.write(binascii.unhexlify(''.join((self.FL_LI_FORMAT['checksum_bch_file'] + self.FL_LI_FORMAT['checksum_fw_image'] + self.FL_LI_FORMAT['application_start_address'] + self.FL_LI_FORMAT['num_memory_instances'] + self.FL_LI_FORMAT['p_app_memory_instance'] + self.FL_LI_FORMAT['first_block_address'] + self.FL_LI_FORMAT['successfully_stored'] + self.FL_LI_FORMAT['checksum_bch_file_header']) *['FF']))) #Process each line in the file prev_address = 0 address = 0 num_bytes = 0 start_address = "" cur_buffer = "" prev_num_bytes = 0 cur_num_bytes = 0 for line in mot_lines: # Modified how we detect RAM records isRamRecord = False if line.startswith('S3') == True: potentialRamAddress = int(line[4:12],16) if ((potentialRamAddress >= 0x1FFE0000) and (potentialRamAddress <= 0x2007FFFF)): isRamRecord = True if isRamRecord == True: print("Stripping out RAM Records: ", line) else: self.CRCTable.ProcessRecord(line) patched_lines=self.CRCTable.GetPatchedRecords() with open(self.mot_filename,'w') as mot_overwritten: mot_overwritten.writelines(patched_lines) #almost identical but we're getting a CRC patched line somewhere in there for line in patched_lines: #Test to see if each line starts with 'S' if line.startswith('S') == False: self.found_error() #Get address for this line #S3 means 4-byte address if line.startswith('S3') == True: address_start_byte = 4 data_start_byte = 12 address_size_bytes = 4 #S2 means 3-byte address elif line.startswith('S2') == True: address_start_byte = 4 data_start_byte = 10 address_size_bytes = 3 #S1 means 2-byte address elif line.startswith('S1') == True: address_start_byte = 4 data_start_byte = 8 address_size_bytes = 2 #You can add more elif statements here for handling other #S-Records. There are S0-S9. I only handle the ones I need else: continue #Read the address for this S-Record line address = int(line[address_start_byte:data_start_byte],16) #Get number of bytes on the line cur_num_bytes = int(line[2:address_start_byte],16) #Get number of bytes between this record and last (0 means they are sequential) bytes_between = address - prev_address - prev_num_bytes + (address_size_bytes + self.CHECKSUM_BYTES) #Get file header if this is the place for it if address <= self.header_location and self.header_location < (address + cur_num_bytes - address_size_bytes - self.CHECKSUM_BYTES): #All or part of the file header is in this buffer #How far into buffer does the file header start offset_in_buffer = self.header_location - address #How many bytes are left after the start of the file load header in buffer buffer_bytes_left = (address + cur_num_bytes - address_size_bytes - self.CHECKSUM_BYTES) - self.header_location if buffer_bytes_left >= self.header_bytes_left: #We can get the whole (or rest) of the file header now self.fileheader += line[data_start_byte+(offset_in_buffer*2):data_start_byte+(offset_in_buffer*2)+(self.header_bytes_left*2)] self.header_bytes_left = 0 else: #We can only get part of the file header this time self.fileheader += line[data_start_byte+(offset_in_buffer*2):data_start_byte+(offset_in_buffer*2)+(buffer_bytes_left*2)] self.header_bytes_left -= buffer_bytes_left self.header_location += buffer_bytes_left #Check if first line of file if len(cur_buffer) == 0: cur_buffer += line[data_start_byte:len(line)-((self.CHECKSUM_BYTES*2)+1)] #Get start address start_address = line[address_start_byte:data_start_byte] #Check to see if address is sequential or within max_fill_space elif bytes_between <= self.max_fill_space and (len(cur_buffer)/2) + bytes_between < self.max_data_size: #Add 0xFF's in empty space to join S-Records that are not sequential if(bytes_between > 0): while(bytes_between > 0): cur_buffer += 'FF' bytes_between -= 1 #Check to see if adding this record will go over the max Data size, if so split it if((len(cur_buffer)/2) + cur_num_bytes - address_size_bytes - self.CHECKSUM_BYTES > self.max_data_size): num_bytes_left = self.max_data_size - (len(cur_buffer)/2) #Multiple num_bytes_left by 2 since data is in ASCII hex cur_buffer += line[data_start_byte:data_start_byte + (2*num_bytes_left)] #Output previous record self.write_block(out_file, cur_buffer, start_address) #Start new record header cur_buffer = line[data_start_byte + (2*num_bytes_left):len(line)-((self.CHECKSUM_BYTES*2)+1)] #Get new start address if(address_size_bytes == 4): start_address = "%08x" % (address + num_bytes_left) elif(address_size_bytes == 3): start_address = "%06x" % (address + num_bytes_left) else: start_address = "%04x" % (address + num_bytes_left) else: cur_buffer += line[data_start_byte:len(line)-((self.CHECKSUM_BYTES*2)+1)] #If not sequential, and not first line, then this is new block else: #Useful debug printout #print('new record ' + hex(address) + ' difference is ' + str(address - prev_address - prev_num_bytes + (address_size_bytes + self.CHECKSUM_BYTES))) #Output previous record self.write_block(out_file, cur_buffer, start_address) #TODO Need to skip areas of flash that do not exist (between code flash, data flash, qspi...) #TODO Need to find the slot addresses and sizes using p_app_memory_instance in the file header. #Sum over the gap between the previous address and the current address. range_for_gap_crc = address - (prev_address + (prev_num_bytes - (address_size_bytes + self.CHECKSUM_BYTES))) self.run_erased_flash(range_for_gap_crc) #Start new record header cur_buffer = line[data_start_byte:len(line)-((self.CHECKSUM_BYTES*2)+1)] #Get new start address start_address = line[address_start_byte:data_start_byte] #Update for next line prev_num_bytes = cur_num_bytes #Update previous address so you can check if next S-Record is sequential prev_address = address #Output last buffer, if there is one if(len(cur_buffer) > 0): #output previous record self.write_block(out_file, cur_buffer, start_address) #Sum over the rest of the flash slot. range_for_gap_crc = self.slot_size - self.size_raw self.run_erased_flash(range_for_gap_crc) #Check to make sure LoadFileHeader was found if self.header_bytes_left > 0: print 'Error - The Load Image Header was not found for this application.' print 'Look at structure of Load Image Header for what is supposed to be found' raise Exception("Error - The Load Image Header was not found for this application.") else: #Process file header and write to file self.ProcessHeader(out_file) #Close output file out_file.close() print "\nS-Record file converted successfully." print "Output file is " + self.out_filename print "Size of entire Load Image is " + str(self.filesize) + " bytes" print self.mot_filename + ' checksum_fw_image: ' + (("%0" + str(self.FL_LI_FORMAT['checksum_fw_image']*2) + "x") % self.checksum_fw_image) #Not all of the information for the Load Image Header is known when we first start processing #the file. This function is called after the file is processed so that we know all the #information we need (image_identifier, version_number, file_crc) def ProcessHeader(self, output_file): #This is used to 'define a structure' so that we can take the data and split it #into its individual parts easily. This would be similar to having a structure pointer #in C and pointing it to a block of memory that you knew represented a structure. The #entries in this string need to be in the same exact order as you have in the #C structure. For instance 'valid_mask' is the first entry and 'image_identifier' is the 2nd. FL_Struct_LI = 'valid_mask version_number image_identifier num_blocks size_bch size_raw maximum_block_size checksum_bch_file checksum_fw_image application_start_address num_memory_instances p_app_memory_instance first_block_address successfully_stored checksum_bch_file_header' #This builds the format string needed. B = byte, H = 2 bytes, L = 4 bytes per entry. #Output will produce something like this '=address and self.crcLoc