# # import argparse import yaml from . import iar_link from . import gcc_link from . import keil_link # Linker file locations. IAR_LD_SCRIPT = "iar/linker_script.icf" KEIL_LD_SCRIPT = "keil/linker_script.sct" KEIL_DEBUG_FILE = "keil/Dbg_RAM.ini" GCC_LD_SCRIPT = "gcc/linker_script.ld" KEIL_STARTUP_FILE = "keil/startup_keil.s" IAR_STARTUP_FILE = "iar/startup_iar.c" GCC_STARTUP_FILE = "gcc/startup_gcc.c" def main(): parser = argparse.ArgumentParser() parser.add_argument('config_file', default='default_config_apollo3.yaml') parser.add_argument('-i', dest='iar', action='store_true') parser.add_argument('-k', dest='keil', action='store_true') parser.add_argument('-g', dest='gcc', action='store_true') args = parser.parse_args() # Read the configuration file. config = read_configuration(args.config_file) # Figure out what we want to build. If no specific toolchains were # specified, just build everything. Otherwise, only build what was # specified. build_all = True if args.keil: write_keil_linker_scripts(config) build_all = False if args.iar: write_iar_linker_scripts(config) build_all = False if args.gcc: write_gcc_linker_scripts(config) build_all = False if build_all: write_keil_linker_scripts(config) write_iar_linker_scripts(config) write_gcc_linker_scripts(config) # Print the memory map print_memory_map(config) def generate_files(config_file, toolchains): config = read_configuration(config_file) print_memory_map(config) if 'keil' in toolchains: write_keil_linker_scripts(config) if 'iar' in toolchains: write_iar_linker_scripts(config) if 'gcc' in toolchains: write_gcc_linker_scripts(config) def read_configuration(config_file): """Read a configuration YAML files and return a dictionary of memory sections""" # Read the YAML configuration file as is. with open(config_file) as file_object: config_string = file_object.read() config = yaml.load(config_string, Loader=yaml.FullLoader) memory_sections = config['MemorySections'] # Search through the memory sections... for name in memory_sections.keys(): # Find the start value, and convert it if necessary. start = memory_sections[name]['start'] memory_sections[name]['start'] = convert_number(start) # If we find a size value, use it. if 'size' in memory_sections[name]: size = memory_sections[name]['size'] memory_sections[name]['size'] = convert_number(size) # It not, try to use an "end" value. elif 'end' in memory_sections[name]: end = memory_sections[name]['end'] memory_sections[name]['size'] = convert_number(end) - convert_number(start) stack_size = config['StackOptions']['size'] config['StackOptions']['size'] = convert_number(stack_size) # Create a memory section for the stack. memory_sections['STACK'] = dict() # Create a section for the stack. To do this, we'll either need to # carve space out of TCM or RWMEM. if config['StackOptions']['place_in_tcm']: if config['StackOptions']['size'] > memory_sections['TCM']['size']: raise LinkerConfigError("Stack ({} B) doesn't fit in TCM ({} B)".format( config['StackOptions']['size'], memory_sections['TCM']['size'])) memory_sections['STACK']['start'] = memory_sections['TCM']['start'] memory_sections['STACK']['size'] = config['StackOptions']['size'] memory_sections['TCM']['start'] = (memory_sections['STACK']['start'] + config['StackOptions']['size']) memory_sections['TCM']['size'] = (memory_sections['TCM']['size'] - config['StackOptions']['size']) else: if config['StackOptions']['size'] > memory_sections['RWMEM']['size']: raise LinkerConfigError("Stack ({} B) doesn't fit in RWMEM ({} B)".format( config['StackOptions']['size'], memory_sections['RWMEM']['size'])) memory_sections['STACK']['start'] = memory_sections['RWMEM']['start'] memory_sections['STACK']['size'] = config['StackOptions']['size'] memory_sections['RWMEM']['start'] = (memory_sections['STACK']['start'] + config['StackOptions']['size']) memory_sections['RWMEM']['size'] = (memory_sections['RWMEM']['size'] - config['StackOptions']['size']) return memory_sections def write_keil_linker_scripts(config): try: linker_file_data, debug_file_data = keil_link.generate_link_script(config) with open(KEIL_LD_SCRIPT, 'w') as linker_file: linker_file.write(linker_file_data) with open(KEIL_DEBUG_FILE, 'w') as debug_file: debug_file.write(debug_file_data) keil_link.fix_startup_file(config, KEIL_STARTUP_FILE) except FileNotFoundError: pass def write_iar_linker_scripts(config): try: with open(IAR_LD_SCRIPT, 'w') as linker_file: linker_file.write(iar_link.generate_link_script(config)) iar_link.fix_startup_file(config, IAR_STARTUP_FILE) except FileNotFoundError: pass def write_gcc_linker_scripts(config): try: with open(GCC_LD_SCRIPT, 'w') as linker_file: linker_file.write(gcc_link.generate_link_script(config)) gcc_link.fix_startup_file(config, GCC_STARTUP_FILE) except FileNotFoundError: pass def convert_number(N): """Take in an integer or a numerical string ending in 'K', and convert it to an int""" if isinstance(N, int): return N elif isinstance(N, str): if N.endswith('K'): return int(N[:-1]) * 1024 else: raise LinkerConfigError('"{}" not recognized as a number'.format(N)) else: raise LinkerConfigError('"{}" not recognized as a number'.format(N)) def print_memory_map(memory_sections): """Show the memory map in a human readable format""" # Sort the section names by their starting address. section_names = sorted(memory_sections.keys(), key=lambda x: memory_sections[x]['start']) # Search for the longest section name, and record its length. max_name_length = max(len(x) for x in section_names) # This is a roundabout way to copy the maximum name length into a format # string, so we can make the output string look pretty. name_format = '{{:{}}}'.format(max_name_length + 1) for name in section_names: section = memory_sections[name] mapping = { 'name': name_format.format(name + ':'), 'start': '0x{:08X}'.format(section['start']), 'end': '0x{:08X}'.format(section['start'] + section['size']), 'size': section['size'], } print('{name} {start:10} - {end:10} ({size} bytes)'.format(**mapping)) # Custom error for linker configuration problems. class LinkerConfigError(Exception): pass if __name__ == '__main__': main()