#!/usr/bin/env python3 # # Copyright (c) 2017 Intel Corporation # Copyright (c) 2018 Foundries.io # Copyright (c) 2023 Nordic Semiconductor NA # # SPDX-License-Identifier: Apache-2.0 # import argparse import sys import os import importlib from elftools.elf.elffile import ELFFile from elftools.elf.sections import SymbolTableSection class gen_isr_log: def __init__(self, debug = False): self.__debug = debug def debug(self, text): """Print debug message if debugging is enabled. Note - this function requires config global variable to be initialized. """ if self.__debug: sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") @staticmethod def error(text): sys.exit(os.path.basename(sys.argv[0]) + ": error: " + text + "\n") def set_debug(self, state): self.__debug = state log = gen_isr_log() class gen_isr_config: """All the constants and configuration gathered in single class for readability. """ # Constants __ISR_FLAG_DIRECT = 1 << 0 __swt_spurious_handler = "z_irq_spurious" __swt_shared_handler = "z_shared_isr" __vt_spurious_handler = "z_irq_spurious" __vt_irq_handler = "_isr_wrapper" __shared_array_name = "z_shared_sw_isr_table" __sw_isr_array_name = "_sw_isr_table" __irq_vector_array_name = "_irq_vector_table" @staticmethod def __bm(bits): return (1 << bits) - 1 def __init__(self, args, syms, log): """Initialize the configuration object. The configuration object initialization takes only arguments as a parameter. This is done to allow debug function work as soon as possible. """ # Store the arguments required for work self.__args = args self.__syms = syms self.__log = log # Select the default interrupt vector handler if self.args.sw_isr_table: self.__vt_default_handler = self.__vt_irq_handler else: self.__vt_default_handler = self.__vt_spurious_handler # Calculate interrupt bits self.__int_bits = [8, 8, 8] # The below few hardware independent magic numbers represent various # levels of interrupts in a multi-level interrupt system. # 0x000000FF - represents the 1st level (i.e. the interrupts # that directly go to the processor). # 0x0000FF00 - represents the 2nd level (i.e. the interrupts funnel # into 1 line which then goes into the 1st level) # 0x00FF0000 - represents the 3rd level (i.e. the interrupts funnel # into 1 line which then goes into the 2nd level) self.__int_lvl_masks = [0x000000FF, 0x0000FF00, 0x00FF0000] self.__irq2_baseoffset = None self.__irq3_baseoffset = None self.__irq2_offsets = None self.__irq3_offsets = None if self.check_multi_level_interrupts(): self.__max_irq_per = self.get_sym("CONFIG_MAX_IRQ_PER_AGGREGATOR") self.__int_bits[0] = self.get_sym("CONFIG_1ST_LEVEL_INTERRUPT_BITS") self.__int_bits[1] = self.get_sym("CONFIG_2ND_LEVEL_INTERRUPT_BITS") self.__int_bits[2] = self.get_sym("CONFIG_3RD_LEVEL_INTERRUPT_BITS") if sum(self.int_bits) > 32: raise ValueError("Too many interrupt bits") self.__int_lvl_masks[0] = self.__bm(self.int_bits[0]) self.__int_lvl_masks[1] = self.__bm(self.int_bits[1]) << self.int_bits[0] self.__int_lvl_masks[2] = self.__bm(self.int_bits[2]) << (self.int_bits[0] + self.int_bits[1]) self.__log.debug("Level Bits Bitmask") self.__log.debug("----------------------------") for i in range(3): bitmask_str = "0x" + format(self.__int_lvl_masks[i], '08X') self.__log.debug(f"{i + 1:>5} {self.__int_bits[i]:>7} {bitmask_str:>14}") if self.check_sym("CONFIG_2ND_LEVEL_INTERRUPTS"): num_aggregators = self.get_sym("CONFIG_NUM_2ND_LEVEL_AGGREGATORS") self.__irq2_baseoffset = self.get_sym("CONFIG_2ND_LVL_ISR_TBL_OFFSET") self.__irq2_offsets = [self.get_sym('CONFIG_2ND_LVL_INTR_{}_OFFSET'. format(str(i).zfill(2))) for i in range(num_aggregators)] self.__log.debug('2nd level offsets: {}'.format(self.__irq2_offsets)) if self.check_sym("CONFIG_3RD_LEVEL_INTERRUPTS"): num_aggregators = self.get_sym("CONFIG_NUM_3RD_LEVEL_AGGREGATORS") self.__irq3_baseoffset = self.get_sym("CONFIG_3RD_LVL_ISR_TBL_OFFSET") self.__irq3_offsets = [self.get_sym('CONFIG_3RD_LVL_INTR_{}_OFFSET'. format(str(i).zfill(2))) for i in range(num_aggregators)] self.__log.debug('3rd level offsets: {}'.format(self.__irq3_offsets)) @property def args(self): return self.__args @property def swt_spurious_handler(self): return self.__swt_spurious_handler @property def swt_shared_handler(self): return self.__swt_shared_handler @property def vt_default_handler(self): return self.__vt_default_handler @property def shared_array_name(self): return self.__shared_array_name @property def sw_isr_array_name(self): return self.__sw_isr_array_name @property def irq_vector_array_name(self): return self.__irq_vector_array_name @property def int_bits(self): return self.__int_bits @property def int_lvl_masks(self): return self.__int_lvl_masks def endian_prefix(self): if self.args.big_endian: return ">" else: return "<" def get_irq_baseoffset(self, lvl): if lvl == 2: return self.__irq2_baseoffset if lvl == 3: return self.__irq3_baseoffset self.__log.error("Unsupported irq level: {}".format(lvl)) def get_irq_index(self, irq, lvl): if lvl == 2: offsets = self.__irq2_offsets elif lvl == 3: offsets = self.__irq3_offsets else: self.__log.error("Unsupported irq level: {}".format(lvl)) try: return offsets.index(irq) except ValueError: self.__log.error("IRQ {} not present in parent offsets ({}). ". format(irq, offsets) + " Recheck interrupt configuration.") def get_swt_table_index(self, offset, irq): if not self.check_multi_level_interrupts(): return irq - offset # Calculate index for multi level interrupts self.__log.debug('IRQ = ' + hex(irq)) irq3 = (irq & self.int_lvl_masks[2]) >> (self.int_bits[0] + self.int_bits[1]) irq2 = (irq & self.int_lvl_masks[1]) >> (self.int_bits[0]) irq1 = irq & self.int_lvl_masks[0] # Figure out third level interrupt position if irq3: list_index = self.get_irq_index(irq2 - 1, 3) irq3_pos = self.get_irq_baseoffset(3) + self.__max_irq_per * list_index + irq3 - 1 self.__log.debug('IRQ_level = 3') self.__log.debug('IRQ_Indx = ' + str(irq3)) self.__log.debug('IRQ_Pos = ' + str(irq3_pos)) return irq3_pos - offset # Figure out second level interrupt position if irq2: list_index = self.get_irq_index(irq1, 2) irq2_pos = self.get_irq_baseoffset(2) + self.__max_irq_per * list_index + irq2 - 1 self.__log.debug('IRQ_level = 2') self.__log.debug('IRQ_Indx = ' + str(irq2)) self.__log.debug('IRQ_Pos = ' + str(irq2_pos)) return irq2_pos - offset # Figure out first level interrupt position self.__log.debug('IRQ_level = 1') self.__log.debug('IRQ_Indx = ' + str(irq1)) self.__log.debug('IRQ_Pos = ' + str(irq1)) return irq1 - offset def get_intlist_snames(self): return self.args.intlist_section def test_isr_direct(self, flags): return flags & self.__ISR_FLAG_DIRECT def get_sym_from_addr(self, addr): for key, value in self.__syms.items(): if addr == value: return key return None def get_sym(self, name): return self.__syms.get(name) def check_sym(self, name): return name in self.__syms def check_multi_level_interrupts(self): return self.check_sym("CONFIG_MULTI_LEVEL_INTERRUPTS") def check_shared_interrupts(self): return self.check_sym("CONFIG_SHARED_INTERRUPTS") def check_64b(self): return self.check_sym("CONFIG_64BIT") def get_symbols(obj): for section in obj.iter_sections(): if isinstance(section, SymbolTableSection): return {sym.name: sym.entry.st_value for sym in section.iter_symbols()} log.error("Could not find symbol table") def read_intList_sect(elfobj, snames): """ Load the raw intList section data in a form of byte array. """ intList_sect = None for sname in snames: intList_sect = elfobj.get_section_by_name(sname) if intList_sect is not None: log.debug("Found intlist section: \"{}\"".format(sname)) break if intList_sect is None: log.error("Cannot find the intlist section!") intdata = intList_sect.data() return intdata def parse_args(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False) parser.add_argument("-e", "--big-endian", action="store_true", help="Target encodes data in big-endian format (little endian is " "the default)") parser.add_argument("-d", "--debug", action="store_true", help="Print additional debugging information") parser.add_argument("-o", "--output-source", required=True, help="Output source file") parser.add_argument("-l", "--linker-output-files", nargs=2, metavar=("vector_table_link", "software_interrupt_link"), help="Output linker files. " "Used only if CONFIG_ISR_TABLES_LOCAL_DECLARATION is enabled. " "In other case empty file would be generated.") parser.add_argument("-k", "--kernel", required=True, help="Zephyr kernel image") parser.add_argument("-s", "--sw-isr-table", action="store_true", help="Generate SW ISR table") parser.add_argument("-V", "--vector-table", action="store_true", help="Generate vector table") parser.add_argument("-i", "--intlist-section", action="append", required=True, help="The name of the section to search for the interrupt data. " "This is accumulative argument. The first section found would be used.") return parser.parse_args() def main(): args = parse_args() # Configure logging as soon as possible log.set_debug(args.debug) with open(args.kernel, "rb") as fp: kernel = ELFFile(fp) config = gen_isr_config(args, get_symbols(kernel), log) intlist_data = read_intList_sect(kernel, config.get_intlist_snames()) if config.check_sym("CONFIG_ISR_TABLES_LOCAL_DECLARATION"): parser_module = importlib.import_module('gen_isr_tables_parser_local') parser = parser_module.gen_isr_parser(intlist_data, config, log) else: parser_module = importlib.import_module('gen_isr_tables_parser_carrays') parser = parser_module.gen_isr_parser(intlist_data, config, log) with open(args.output_source, "w") as fp: parser.write_source(fp) if args.linker_output_files is not None: with open(args.linker_output_files[0], "w") as fp_vt, \ open(args.linker_output_files[1], "w") as fp_swi: if hasattr(parser, 'write_linker_vt'): parser.write_linker_vt(fp_vt) else: log.debug("Chosen parser does not support vector table linker file") fp_vt.write('/* Empty */\n') if hasattr(parser, 'write_linker_swi'): parser.write_linker_swi(fp_swi) else: log.debug("Chosen parser does not support software interrupt linker file") fp_swi.write('/* Empty */\n') if __name__ == "__main__": main()