| #!/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 struct |
| |
| class gen_isr_parser: |
| source_header = """ |
| /* AUTO-GENERATED by gen_isr_tables.py, do not edit! */ |
| |
| #include <zephyr/toolchain.h> |
| #include <zephyr/linker/sections.h> |
| #include <zephyr/sw_isr_table.h> |
| #include <zephyr/arch/cpu.h> |
| |
| """ |
| |
| shared_isr_table_header = """ |
| |
| /* For this parser to work, we have to be sure that shared interrupts table entry |
| * and the normal isr table entry have exactly the same layout |
| */ |
| BUILD_ASSERT(sizeof(struct _isr_table_entry) |
| == |
| sizeof(struct z_shared_isr_table_entry), |
| "Shared ISR and ISR table entries layout do not match"); |
| BUILD_ASSERT(offsetof(struct _isr_table_entry, arg) |
| == |
| offsetof(struct z_shared_isr_table_entry, arg), |
| "Shared ISR and ISR table entries layout do not match"); |
| BUILD_ASSERT(offsetof(struct _isr_table_entry, isr) |
| == |
| offsetof(struct z_shared_isr_table_entry, isr), |
| "Shared ISR and ISR table entries layout do not match"); |
| |
| """ |
| |
| def __init__(self, intlist_data, config, log): |
| """Initialize the parser. |
| |
| The function prepares parser to work. |
| Parameters: |
| - intlist_data: The binnary data from intlist section |
| - config: The configuration object |
| - log: The logging object, has to have error and debug methods |
| """ |
| self.__config = config |
| self.__log = log |
| intlist = self.__read_intlist(intlist_data) |
| self.__vt, self.__swt, self.__nv, header = self.__parse_intlist(intlist) |
| self.__swi_table_entry_size = header["swi_table_entry_size"] |
| self.__shared_isr_table_entry_size = header["shared_isr_table_entry_size"] |
| self.__shared_isr_client_num_offset = header["shared_isr_client_num_offset"] |
| |
| def __read_intlist(self, intlist_data): |
| """read an intList section from the elf file. |
| This is version 2 of a header created by include/zephyr/linker/intlist.ld: |
| |
| struct { |
| uint32_t num_vectors; <- typically CONFIG_NUM_IRQS |
| uint8_t stream[]; <- the stream with the interrupt data |
| }; |
| |
| The stream is contained from variable length records in a form: |
| |
| struct _isr_list_sname { |
| /** IRQ line number */ |
| int32_t irq; |
| /** Flags for this IRQ, see ISR_FLAG_* definitions */ |
| int32_t flags; |
| /** The section name */ |
| const char sname[]; |
| }; |
| |
| The flexible array member here (sname) contains the name of the section where the structure |
| with interrupt data is located. |
| It is always Null-terminated string thus we have to search through the input data for the |
| structure end. |
| |
| """ |
| intlist = {} |
| prefix = self.__config.endian_prefix() |
| |
| # Extract header and the rest of the data |
| intlist_header_fmt = prefix + "IIIII" |
| header_sz = struct.calcsize(intlist_header_fmt) |
| header_raw = struct.unpack_from(intlist_header_fmt, intlist_data, 0) |
| self.__log.debug(str(header_raw)) |
| |
| intlist["num_vectors"] = header_raw[0] |
| intlist["offset"] = header_raw[1] |
| intlist["swi_table_entry_size"] = header_raw[2] |
| intlist["shared_isr_table_entry_size"] = header_raw[3] |
| intlist["shared_isr_client_num_offset"] = header_raw[4] |
| |
| intdata = intlist_data[header_sz:] |
| |
| # Extract information about interrupts |
| intlist_entry_fmt = prefix + "ii" |
| entry_sz = struct.calcsize(intlist_entry_fmt) |
| intlist["interrupts"] = [] |
| |
| while len(intdata) > entry_sz: |
| entry_raw = struct.unpack_from(intlist_entry_fmt, intdata, 0) |
| intdata = intdata[entry_sz:] |
| null_idx = intdata.find(0) |
| if null_idx < 0: |
| self.__log.error("Cannot find sname null termination at IRQ{}".format(entry_raw[0])) |
| bname = intdata[:null_idx] |
| # Next structure starts with 4B alignment |
| next_idx = null_idx + 1 |
| next_idx = (next_idx + 3) & ~3 |
| intdata = intdata[next_idx:] |
| sname = bname.decode() |
| intlist["interrupts"].append([entry_raw[0], entry_raw[1], sname]) |
| self.__log.debug("Unpacked IRQ{}, flags: {}, sname: \"{}\"\n".format( |
| entry_raw[0], entry_raw[1], sname)) |
| |
| # If any data left at the end - it has to be all the way 0 - this is just a check |
| if (len(intdata) and not all([d == 0 for d in intdata])): |
| self.__log.error("Non-zero data found at the end of the intList data.\n") |
| |
| self.__log.debug("Configured interrupt routing with linker") |
| self.__log.debug("irq flags sname") |
| self.__log.debug("--------------------------") |
| |
| for irq in intlist["interrupts"]: |
| self.__log.debug("{0:<3} {1:<5} {2}".format( |
| hex(irq[0]), irq[1], irq[2])) |
| |
| return intlist |
| |
| def __parse_intlist(self, intlist): |
| """All the intlist data are parsed into swt and vt arrays. |
| |
| The vt array is prepared for hardware interrupt table. |
| Every entry in the selected position would contain None or the name of the function pointer |
| (address or string). |
| |
| The swt is a little more complex. At every position it would contain an array of parameter and |
| function pointer pairs. If CONFIG_SHARED_INTERRUPTS is enabled there may be more than 1 entry. |
| If empty array is placed on selected position - it means that the application does not implement |
| this interrupt. |
| |
| Parameters: |
| - intlist: The preprocessed list of intlist section content (see read_intlist) |
| |
| Return: |
| vt, swt - parsed vt and swt arrays (see function description above) |
| """ |
| nvec = intlist["num_vectors"] |
| offset = intlist["offset"] |
| header = { |
| "swi_table_entry_size": intlist["swi_table_entry_size"], |
| "shared_isr_table_entry_size": intlist["shared_isr_table_entry_size"], |
| "shared_isr_client_num_offset": intlist["shared_isr_client_num_offset"] |
| } |
| |
| if nvec > pow(2, 15): |
| raise ValueError('nvec is too large, check endianness.') |
| |
| self.__log.debug('offset is ' + str(offset)) |
| self.__log.debug('num_vectors is ' + str(nvec)) |
| |
| # Set default entries in both tables |
| if not(self.__config.args.sw_isr_table or self.__config.args.vector_table): |
| self.__log.error("one or both of -s or -V needs to be specified on command line") |
| if self.__config.args.vector_table: |
| vt = [None for i in range(nvec)] |
| else: |
| vt = None |
| if self.__config.args.sw_isr_table: |
| swt = [[] for i in range(nvec)] |
| else: |
| swt = None |
| |
| # Process intlist and write to the tables created |
| for irq, flags, sname in intlist["interrupts"]: |
| if self.__config.test_isr_direct(flags): |
| if not 0 <= irq - offset < len(vt): |
| self.__log.error("IRQ %d (offset=%d) exceeds the maximum of %d" % |
| (irq - offset, offset, len(vt) - 1)) |
| vt[irq - offset] = sname |
| else: |
| # Regular interrupt |
| if not swt: |
| self.__log.error("Regular Interrupt %d declared with section name %s " |
| "but no SW ISR_TABLE in use" |
| % (irq, sname)) |
| |
| table_index = self.__config.get_swt_table_index(offset, irq) |
| |
| if not 0 <= table_index < len(swt): |
| self.__log.error("IRQ %d (offset=%d) exceeds the maximum of %d" % |
| (table_index, offset, len(swt) - 1)) |
| # Check if the given section name does not repeat outside of current interrupt |
| for i in range(nvec): |
| if i == irq: |
| continue |
| if sname in swt[i]: |
| self.__log.error(("Attempting to register the same section name \"{}\"for" + |
| "different interrupts: {} and {}").format(sname, i, irq)) |
| if self.__config.check_shared_interrupts(): |
| lst = swt[table_index] |
| if len(lst) >= self.__config.get_sym("CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS"): |
| self.__log.error(f"Reached shared interrupt client limit. Maybe increase" |
| + f" CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS?") |
| else: |
| if len(swt[table_index]) > 0: |
| self.__log.error(f"multiple registrations at table_index {table_index} for irq {irq} (0x{irq:x})" |
| + f"\nExisting section {swt[table_index]}, new section {sname}" |
| + "\nHas IRQ_CONNECT or IRQ_DIRECT_CONNECT accidentally been invoked on the same irq multiple times?" |
| ) |
| swt[table_index].append(sname) |
| |
| return vt, swt, nvec, header |
| |
| @staticmethod |
| def __irq_spurious_section(irq): |
| return '.irq_spurious.0x{:x}'.format(irq) |
| |
| @staticmethod |
| def __isr_generated_section(irq): |
| return '.isr_generated.0x{:x}'.format(irq) |
| |
| @staticmethod |
| def __shared_entry_section(irq, ent): |
| return '.isr_shared.0x{:x}_0x{:x}'.format(irq, ent) |
| |
| @staticmethod |
| def __shared_client_num_section(irq): |
| return '.isr_shared.0x{:x}_client_num'.format(irq) |
| |
| def __isr_spurious_entry(self, irq): |
| return '_Z_ISR_TABLE_ENTRY({irq}, {func}, NULL, "{sect}");'.format( |
| irq = irq, |
| func = self.__config.swt_spurious_handler, |
| sect = self.__isr_generated_section(irq) |
| ) |
| |
| def __isr_shared_entry(self, irq): |
| return '_Z_ISR_TABLE_ENTRY({irq}, {func}, {arg}, "{sect}");'.format( |
| irq = irq, |
| arg = '&{}[{}]'.format(self.__config.shared_array_name, irq), |
| func = self.__config.swt_shared_handler, |
| sect = self.__isr_generated_section(irq) |
| ) |
| |
| def __irq_spurious_entry(self, irq): |
| return '_Z_ISR_DIRECT_TABLE_ENTRY({irq}, {func}, "{sect}");'.format( |
| irq = irq, |
| func = self.__config.vt_default_handler, |
| sect = self.__irq_spurious_section(irq) |
| ) |
| |
| def __write_isr_handlers(self, fp): |
| for i in range(self.__nv): |
| if len(self.__swt[i]) <= 0: |
| fp.write(self.__isr_spurious_entry(i) + '\n') |
| elif len(self.__swt[i]) > 1: |
| # Connect to shared handlers |
| fp.write(self.__isr_shared_entry(i) + '\n') |
| else: |
| fp.write('/* ISR: {} implemented in app in "{}" section. */\n'.format( |
| i, self.__swt[i][0])) |
| |
| def __write_irq_handlers(self, fp): |
| for i in range(self.__nv): |
| if self.__vt[i] is None: |
| fp.write(self.__irq_spurious_entry(i) + '\n') |
| else: |
| fp.write('/* ISR: {} implemented in app. */\n'.format(i)) |
| |
| def __write_shared_handlers(self, fp): |
| fp.write("extern struct z_shared_isr_table_entry " |
| "{}[{}];\n".format(self.__config.shared_array_name, self.__nv)) |
| |
| shared_cnt = self.__config.get_sym('CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS') |
| for i in range(self.__nv): |
| swt_len = len(self.__swt[i]) |
| for j in range(shared_cnt): |
| if (swt_len <= 1) or (swt_len <= j): |
| # Add all unused entry |
| fp.write('static Z_DECL_ALIGN(struct _isr_table_entry)\n' + |
| '\tZ_GENERIC_SECTION({})\n'.format(self.__shared_entry_section(i, j)) + |
| '\t__used isr_shared_empty_entry_0x{:x}_0x{:x} = {{\n'.format(i, j) + |
| '\t\t.arg = (const void *)NULL,\n' + |
| '\t\t.isr = (void (*)(const void *))(void *)0\n' + |
| '};\n' |
| ) |
| else: |
| # Add information about entry implemented by application |
| fp.write('/* Shared isr {} entry {} implemented in "{}" section*/\n'.format( |
| i, j, self.__swt[i][j])) |
| |
| # Add information about clients count |
| fp.write(('static size_t Z_GENERIC_SECTION({}) __used\n' + |
| 'isr_shared_client_num_0x{:x} = {};\n\n').format( |
| self.__shared_client_num_section(i), |
| i, |
| 0 if swt_len < 2 else swt_len) |
| ) |
| |
| def write_source(self, fp): |
| fp.write(self.source_header) |
| |
| if self.__vt: |
| self.__write_irq_handlers(fp) |
| |
| if not self.__swt: |
| return |
| |
| if self.__config.check_shared_interrupts(): |
| self.__write_shared_handlers(fp) |
| |
| self.__write_isr_handlers(fp) |
| |
| def __write_linker_irq(self, fp): |
| fp.write('{} = .;\n'.format(self.__config.irq_vector_array_name)) |
| for i in range(self.__nv): |
| if self.__vt[i] is None: |
| sname = self.__irq_spurious_section(i) |
| else: |
| sname = self.__vt[i] |
| fp.write('KEEP(*("{}"))\n'.format(sname)) |
| |
| def __write_linker_shared(self, fp): |
| fp.write(". = ALIGN({});\n".format(self.__shared_isr_table_entry_size)) |
| fp.write('{} = .;\n'.format(self.__config.shared_array_name)) |
| shared_cnt = self.__config.get_sym('CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS') |
| client_num_pads = self.__shared_isr_client_num_offset - \ |
| shared_cnt * self.__swi_table_entry_size |
| if client_num_pads < 0: |
| self.__log.error("Invalid __shared_isr_client_num_offset header value") |
| for i in range(self.__nv): |
| swt_len = len(self.__swt[i]) |
| # Add all entries |
| for j in range(shared_cnt): |
| if (swt_len <= 1) or (swt_len <= j): |
| fp.write('KEEP(*("{}"))\n'.format(self.__shared_entry_section(i, j))) |
| else: |
| sname = self.__swt[i][j] |
| if (j != 0) and (sname in self.__swt[i][0:j]): |
| fp.write('/* Repetition of "{}" section */\n'.format(sname)) |
| else: |
| fp.write('KEEP(*("{}"))\n'.format(sname)) |
| fp.write('. = . + {};\n'.format(client_num_pads)) |
| fp.write('KEEP(*("{}"))\n'.format(self.__shared_client_num_section(i))) |
| fp.write(". = ALIGN({});\n".format(self.__shared_isr_table_entry_size)) |
| |
| def __write_linker_isr(self, fp): |
| fp.write(". = ALIGN({});\n".format(self.__swi_table_entry_size)) |
| fp.write('{} = .;\n'.format(self.__config.sw_isr_array_name)) |
| for i in range(self.__nv): |
| if (len(self.__swt[i])) == 1: |
| sname = self.__swt[i][0] |
| else: |
| sname = self.__isr_generated_section(i) |
| fp.write('KEEP(*("{}"))\n'.format(sname)) |
| |
| def write_linker_vt(self, fp): |
| if self.__vt: |
| self.__write_linker_irq(fp) |
| |
| def write_linker_swi(self, fp): |
| if self.__swt: |
| self.__write_linker_isr(fp) |
| |
| if self.__config.check_shared_interrupts(): |
| self.__write_linker_shared(fp) |