#!/usr/bin/env python3 # # Copyright (c) 2017 Intel Corporation # Copyright (c) 2018 Foundries.io # # SPDX-License-Identifier: Apache-2.0 # import argparse import struct import sys import os from elftools.elf.elffile import ELFFile from elftools.elf.sections import SymbolTableSection ISR_FLAG_DIRECT = 1 << 0 # 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) FIRST_LVL_INTERRUPTS = 0x000000FF SECND_LVL_INTERRUPTS = 0x0000FF00 THIRD_LVL_INTERRUPTS = 0x00FF0000 def debug(text): if args.debug: sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") def error(text): sys.exit(os.path.basename(sys.argv[0]) + ": error: " + text + "\n") def endian_prefix(): if args.big_endian: return ">" else: return "<" def read_intlist(intlist_path, syms): """read a binary file containing the contents of the kernel's .intList section. This is an instance of a header created by include/zephyr/linker/intlist.ld: struct { uint32_t num_vectors; <- typically CONFIG_NUM_IRQS struct _isr_list isrs[]; <- Usually of smaller size than num_vectors } Followed by instances of struct _isr_list created by IRQ_CONNECT() calls: struct _isr_list { /** IRQ line number */ int32_t irq; /** Flags for this IRQ, see ISR_FLAG_* definitions */ int32_t flags; /** ISR to call */ void *func; /** Parameter for non-direct IRQs */ const void *param; }; """ intlist = {} prefix = endian_prefix() intlist_header_fmt = prefix + "II" if "CONFIG_64BIT" in syms: intlist_entry_fmt = prefix + "iiQQ" else: intlist_entry_fmt = prefix + "iiII" with open(intlist_path, "rb") as fp: intdata = fp.read() header_sz = struct.calcsize(intlist_header_fmt) header = struct.unpack_from(intlist_header_fmt, intdata, 0) intdata = intdata[header_sz:] debug(str(header)) intlist["num_vectors"] = header[0] intlist["offset"] = header[1] intlist["interrupts"] = [i for i in struct.iter_unpack(intlist_entry_fmt, intdata)] debug("Configured interrupt routing") debug("handler irq flags param") debug("--------------------------") for irq in intlist["interrupts"]: debug("{0:<10} {1:<3} {2:<3} {3}".format( hex(irq[2]), irq[0], irq[1], hex(irq[3]))) return intlist def parse_args(): global 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("-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", required=True, help="Zephyr intlist binary for intList extraction") args = parser.parse_args() source_assembly_header = """ #ifndef ARCH_IRQ_VECTOR_JUMP_CODE #error "ARCH_IRQ_VECTOR_JUMP_CODE not defined" #endif """ def get_symbol_from_addr(syms, addr): for key, value in syms.items(): if addr == value: return key return None def write_code_irq_vector_table(fp, vt, nv, syms): fp.write(source_assembly_header) fp.write("void __irq_vector_table __attribute__((naked)) _irq_vector_table(void) {\n") for i in range(nv): func = vt[i] if isinstance(func, int): func_as_string = get_symbol_from_addr(syms, func) else: func_as_string = func fp.write("\t__asm(ARCH_IRQ_VECTOR_JUMP_CODE({}));\n".format(func_as_string)) fp.write("}\n") def write_address_irq_vector_table(fp, vt, nv): fp.write("uintptr_t __irq_vector_table _irq_vector_table[%d] = {\n" % nv) for i in range(nv): func = vt[i] if isinstance(func, int): fp.write("\t{},\n".format(vt[i])) else: fp.write("\t((uintptr_t)&{}),\n".format(vt[i])) fp.write("};\n") source_header = """ /* AUTO-GENERATED by gen_isr_tables.py, do not edit! */ #include #include #include #include typedef void (* ISR)(const void *); """ def write_source_file(fp, vt, swt, intlist, syms): fp.write(source_header) nv = intlist["num_vectors"] if vt: if "CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_ADDRESS" in syms: write_address_irq_vector_table(fp, vt, nv) elif "CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE" in syms: write_code_irq_vector_table(fp, vt, nv, syms) else: error("CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_{ADDRESS,CODE} not set") if not swt: return fp.write("struct _isr_table_entry __sw_isr_table _sw_isr_table[%d] = {\n" % nv) level2_offset = syms.get("CONFIG_2ND_LVL_ISR_TBL_OFFSET") level3_offset = syms.get("CONFIG_3RD_LVL_ISR_TBL_OFFSET") for i in range(nv): param, func = swt[i] if isinstance(func, int): func_as_string = "{0:#x}".format(func) else: func_as_string = func if level2_offset is not None and i == level2_offset: fp.write("\t/* Level 2 interrupts start here (offset: {}) */\n". format(level2_offset)) if level3_offset is not None and i == level3_offset: fp.write("\t/* Level 3 interrupts start here (offset: {}) */\n". format(level3_offset)) fp.write("\t{{(const void *){0:#x}, (ISR){1}}},\n".format(param, func_as_string)) fp.write("};\n") 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()} error("Could not find symbol table") def getindex(irq, irq_aggregator_pos): try: return irq_aggregator_pos.index(irq) except ValueError: error("IRQ {} not present in parent offsets ({}). ". format(irq, irq_aggregator_pos) + " Recheck interrupt configuration.") def main(): parse_args() with open(args.kernel, "rb") as fp: kernel = ELFFile(fp) syms = get_symbols(kernel) if "CONFIG_MULTI_LEVEL_INTERRUPTS" in syms: max_irq_per = syms["CONFIG_MAX_IRQ_PER_AGGREGATOR"] if "CONFIG_2ND_LEVEL_INTERRUPTS" in syms: num_aggregators = syms["CONFIG_NUM_2ND_LEVEL_AGGREGATORS"] irq2_baseoffset = syms["CONFIG_2ND_LVL_ISR_TBL_OFFSET"] list_2nd_lvl_offsets = [syms['CONFIG_2ND_LVL_INTR_{}_OFFSET'. format(str(i).zfill(2))] for i in range(num_aggregators)] debug('2nd level offsets: {}'.format(list_2nd_lvl_offsets)) if "CONFIG_3RD_LEVEL_INTERRUPTS" in syms: num_aggregators = syms["CONFIG_NUM_3RD_LEVEL_AGGREGATORS"] irq3_baseoffset = syms["CONFIG_3RD_LVL_ISR_TBL_OFFSET"] list_3rd_lvl_offsets = [syms['CONFIG_3RD_LVL_INTR_{}_OFFSET'. format(str(i).zfill(2))] for i in range(num_aggregators)] debug('3rd level offsets: {}'.format(list_3rd_lvl_offsets)) intlist = read_intlist(args.intlist, syms) nvec = intlist["num_vectors"] offset = intlist["offset"] if nvec > pow(2, 15): raise ValueError('nvec is too large, check endianness.') swt_spurious_handler = "((uintptr_t)&z_irq_spurious)" vt_spurious_handler = "z_irq_spurious" vt_irq_handler = "_isr_wrapper" debug('offset is ' + str(offset)) debug('num_vectors is ' + str(nvec)) # Set default entries in both tables if args.sw_isr_table: # All vectors just jump to the common vt_irq_handler. If some entries # are used for direct interrupts, they will be replaced later. if args.vector_table: vt = [vt_irq_handler for i in range(nvec)] else: vt = None # Default to spurious interrupt handler. Configured interrupts # will replace these entries. swt = [(0, swt_spurious_handler) for i in range(nvec)] else: if args.vector_table: vt = [vt_spurious_handler for i in range(nvec)] else: error("one or both of -s or -V needs to be specified on command line") swt = None for irq, flags, func, param in intlist["interrupts"]: if flags & ISR_FLAG_DIRECT: if param != 0: error("Direct irq %d declared, but has non-NULL parameter" % irq) if not 0 <= irq - offset < len(vt): error("IRQ %d (offset=%d) exceeds the maximum of %d" % (irq - offset, offset, len(vt) - 1)) vt[irq - offset] = func else: # Regular interrupt if not swt: error("Regular Interrupt %d declared with parameter 0x%x " "but no SW ISR_TABLE in use" % (irq, param)) if not "CONFIG_MULTI_LEVEL_INTERRUPTS" in syms: table_index = irq - offset else: # Figure out third level interrupt position debug('IRQ = ' + hex(irq)) irq3 = (irq & THIRD_LVL_INTERRUPTS) >> 16 irq2 = (irq & SECND_LVL_INTERRUPTS) >> 8 irq1 = irq & FIRST_LVL_INTERRUPTS if irq3: irq_parent = irq2 list_index = getindex(irq_parent, list_3rd_lvl_offsets) irq3_pos = irq3_baseoffset + max_irq_per*list_index + irq3 - 1 debug('IRQ_level = 3') debug('IRQ_Indx = ' + str(irq3)) debug('IRQ_Pos = ' + str(irq3_pos)) table_index = irq3_pos - offset # Figure out second level interrupt position elif irq2: irq_parent = irq1 list_index = getindex(irq_parent, list_2nd_lvl_offsets) irq2_pos = irq2_baseoffset + max_irq_per*list_index + irq2 - 1 debug('IRQ_level = 2') debug('IRQ_Indx = ' + str(irq2)) debug('IRQ_Pos = ' + str(irq2_pos)) table_index = irq2_pos - offset # Figure out first level interrupt position else: debug('IRQ_level = 1') debug('IRQ_Indx = ' + str(irq1)) debug('IRQ_Pos = ' + str(irq1)) table_index = irq1 - offset if not 0 <= table_index < len(swt): error("IRQ %d (offset=%d) exceeds the maximum of %d" % (table_index, offset, len(swt) - 1)) if swt[table_index] != (0, swt_spurious_handler): error(f"multiple registrations at table_index {table_index} for irq {irq} (0x{irq:x})" + f"\nExisting handler 0x{swt[table_index][1]:x}, new handler 0x{func:x}" + "\nHas IRQ_CONNECT or IRQ_DIRECT_CONNECT accidentally been invoked on the same irq multiple times?" ) swt[table_index] = (param, func) with open(args.output_source, "w") as fp: write_source_file(fp, vt, swt, intlist, syms) if __name__ == "__main__": main()