scripts: build: gen_isr_tables: Implement local ISR generation

This commit moves all the functionality related to the current
interrupt parser into gen_isr_tables_parser_carrays.py file.
The new parser file gen_isr_tables_parser_local.py file is
implemented with the new parser that.
Additional information added to the generated interrupt header
that contains data required by the new parser.

Signed-off-by: Radosław Koppel <radoslaw.koppel@nordicsemi.no>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ce9121b..a187601 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1228,10 +1228,11 @@
   # isr_tables.c is generated from ${ZEPHYR_LINK_STAGE_EXECUTABLE} by
   # gen_isr_tables.py
   add_custom_command(
-    OUTPUT isr_tables.c
+    OUTPUT isr_tables.c isr_tables_vt.ld isr_tables_swi.ld
     COMMAND ${PYTHON_EXECUTABLE}
     ${ZEPHYR_BASE}/scripts/build/gen_isr_tables.py
     --output-source isr_tables.c
+    --linker-output-files isr_tables_vt.ld isr_tables_swi.ld
     --kernel $<TARGET_FILE:${ZEPHYR_LINK_STAGE_EXECUTABLE}>
     --intlist-section .intList
     --intlist-section intList
diff --git a/arch/common/isr_tables.c b/arch/common/isr_tables.c
index 9677c92..050597b 100644
--- a/arch/common/isr_tables.c
+++ b/arch/common/isr_tables.c
@@ -15,6 +15,11 @@
 struct int_list_header {
 	uint32_t table_size;
 	uint32_t offset;
+#if IS_ENABLED(CONFIG_ISR_TABLES_LOCAL_DECLARATION)
+	uint32_t swi_table_entry_size;
+	uint32_t shared_isr_table_entry_size;
+	uint32_t shared_isr_client_num_offset;
+#endif /* IS_ENABLED(CONFIG_ISR_TABLES_LOCAL_DECLARATION) */
 };
 
 /* These values are not included in the resulting binary, but instead form the
@@ -24,6 +29,13 @@
 Z_GENERIC_SECTION(.irq_info) __used struct int_list_header _iheader = {
 	.table_size = IRQ_TABLE_SIZE,
 	.offset = CONFIG_GEN_IRQ_START_VECTOR,
+#if IS_ENABLED(CONFIG_ISR_TABLES_LOCAL_DECLARATION)
+	.swi_table_entry_size = sizeof(struct _isr_table_entry),
+#if IS_ENABLED(CONFIG_SHARED_INTERRUPTS)
+	.shared_isr_table_entry_size = sizeof(struct z_shared_isr_table_entry),
+	.shared_isr_client_num_offset = offsetof(struct z_shared_isr_table_entry, client_num),
+#endif /* IS_ENABLED(CONFIG_SHARED_INTERRUPTS) */
+#endif /* IS_ENABLED(CONFIG_ISR_TABLES_LOCAL_DECLARATION) */
 };
 
 /* These are placeholder tables. They will be replaced by the real tables
diff --git a/scripts/build/gen_isr_tables.py b/scripts/build/gen_isr_tables.py
index 3032d1e..84812d7 100755
--- a/scripts/build/gen_isr_tables.py
+++ b/scripts/build/gen_isr_tables.py
@@ -8,9 +8,9 @@
 #
 
 import argparse
-import struct
 import sys
 import os
+import importlib
 from elftools.elf.elffile import ELFFile
 from elftools.elf.sections import SymbolTableSection
 
@@ -44,10 +44,13 @@
     """
     # Constants
     __ISR_FLAG_DIRECT = 1 << 0
-    __swt_spurious_handler = "((uintptr_t)&z_irq_spurious)"
-    __swt_shared_handler = "((uintptr_t)&z_shared_isr)"
+    __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):
@@ -141,6 +144,18 @@
         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
 
@@ -233,289 +248,6 @@
         return self.check_sym("CONFIG_64BIT")
 
 
-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>
-
-typedef void (* ISR)(const void *);
-"""
-
-    source_assembly_header = """
-#ifndef ARCH_IRQ_VECTOR_JUMP_CODE
-#error "ARCH_IRQ_VECTOR_JUMP_CODE not defined"
-#endif
-"""
-
-    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 = self.__parse_intlist(intlist)
-
-    def __read_intlist(self, intlist_data):
-        """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 = self.__config.endian_prefix()
-
-        # Extract header and the rest of the data
-        intlist_header_fmt = prefix + "II"
-        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]
-        intdata = intlist_data[header_sz:]
-
-        # Extract information about interrupts
-        if self.__config.check_64b():
-            intlist_entry_fmt = prefix + "iiQQ"
-        else:
-            intlist_entry_fmt = prefix + "iiII"
-
-        intlist["interrupts"] = [i for i in
-                struct.iter_unpack(intlist_entry_fmt, intdata)]
-
-        self.__log.debug("Configured interrupt routing")
-        self.__log.debug("handler    irq flags param")
-        self.__log.debug("--------------------------")
-
-        for irq in intlist["interrupts"]:
-            self.__log.debug("{0:<10} {1:<3} {2:<3}   {3}".format(
-                hex(irq[2]), irq[0], irq[1], hex(irq[3])))
-
-        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"]
-
-        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, func, param in intlist["interrupts"]:
-            if not vt:
-                error("Direct Interrupt %d declared with parameter 0x%x "
-                      "but no vector table in use"
-                      % (irq, param))
-            if self.__config.test_isr_direct(flags):
-                if param != 0:
-                    self.__log.error("Direct irq %d declared, but has non-NULL parameter"
-                            % irq)
-                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] = func
-            else:
-                # Regular interrupt
-                if not swt:
-                    self.__log.error("Regular Interrupt %d declared with parameter 0x%x "
-                                     "but no SW ISR_TABLE in use"
-                                     % (irq, param))
-
-                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))
-                if self.__config.check_shared_interrupts():
-                    lst = swt[table_index]
-                    if (param, func) in lst:
-                        self.__log.error("Attempting to register the same ISR/arg pair twice.")
-                    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 handler 0x{swt[table_index][0][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].append((param, func))
-
-        return vt, swt, nvec
-
-    def __write_code_irq_vector_table(self, fp):
-        fp.write(self.source_assembly_header)
-
-        fp.write("void __irq_vector_table __attribute__((naked)) _irq_vector_table(void) {\n")
-        for i in range(self.__nv):
-            func = self.__vt[i]
-
-            if func is None:
-                func = self.__config.vt_default_handler
-
-            if isinstance(func, int):
-                func_as_string = self.__config.get_sym_from_addr(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(self, fp):
-        fp.write("uintptr_t __irq_vector_table _irq_vector_table[%d] = {\n" % self.__nv)
-        for i in range(self.__nv):
-            func = self.__vt[i]
-
-            if func is None:
-                func = self.__config.vt_default_handler
-
-            if isinstance(func, int):
-                fp.write("\t{},\n".format(func))
-            else:
-                fp.write("\t((uintptr_t)&{}),\n".format(func))
-
-        fp.write("};\n")
-
-    def __write_shared_table(self, fp):
-        fp.write("struct z_shared_isr_table_entry __shared_sw_isr_table"
-                " z_shared_sw_isr_table[%d] = {\n" % self.__nv)
-
-        for i in range(self.__nv):
-            if self.__swt[i] is None:
-                client_num = 0
-                client_list = None
-            else:
-                client_num = len(self.__swt[i])
-                client_list = self.__swt[i]
-
-            if client_num <= 1:
-                fp.write("\t{ },\n")
-            else:
-                fp.write(f"\t{{ .client_num = {client_num}, .clients = {{ ")
-                for j in range(0, client_num):
-                    routine = client_list[j][1]
-                    arg = client_list[j][0]
-
-                    fp.write(f"{{ .isr = (ISR){ hex(routine) if isinstance(routine, int) else routine }, "
-                            f".arg = (const void *){hex(arg)} }},")
-
-                fp.write(" },\n},\n")
-
-        fp.write("};\n")
-
-    def write_source(self, fp):
-        fp.write(self.source_header)
-
-        if self.__config.check_shared_interrupts():
-            self.__write_shared_table(fp)
-
-        if self.__vt:
-            if self.__config.check_sym("CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_ADDRESS"):
-                self.__write_address_irq_vector_table(fp)
-            elif self.__config.check_sym("CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE"):
-                self.__write_code_irq_vector_table(fp)
-            else:
-                self.__log.error("CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_{ADDRESS,CODE} not set")
-
-        if not self.__swt:
-            return
-
-        fp.write("struct _isr_table_entry __sw_isr_table _sw_isr_table[%d] = {\n"
-                % self.__nv)
-
-        level2_offset = self.__config.get_irq_baseoffset(2)
-        level3_offset = self.__config.get_irq_baseoffset(3)
-
-        for i in range(self.__nv):
-            if len(self.__swt[i]) == 0:
-                # Not used interrupt
-                param = "0x0"
-                func = self.__config.swt_spurious_handler
-            elif len(self.__swt[i]) == 1:
-                # Single interrupt
-                param = "{0:#x}".format(self.__swt[i][0][0])
-                func = self.__swt[i][0][1]
-            else:
-                # Shared interrupt
-                param = "&z_shared_sw_isr_table[{0}]".format(i)
-                func = self.__config.swt_shared_handler
-
-            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}, (ISR){1}}}, /* {2} */\n".format(param, func_as_string, i))
-        fp.write("};\n")
-
-
 def get_symbols(obj):
     for section in obj.iter_sections():
         if isinstance(section, SymbolTableSection):
@@ -554,6 +286,12 @@
             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",
@@ -576,11 +314,31 @@
         config = gen_isr_config(args, get_symbols(kernel), log)
         intlist_data = read_intList_sect(kernel, config.get_intlist_snames())
 
-        parser = gen_isr_parser(intlist_data, config, log)
+        if config.check_sym("CONFIG_ISR_TABLES_LOCAL_DECLARATION"):
+            sys.stdout.write(
+                "Warning: The EXPERIMENTAL ISR_TABLES_LOCAL_DECLARATION feature selected\n")
+            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()
diff --git a/scripts/build/gen_isr_tables_parser_carrays.py b/scripts/build/gen_isr_tables_parser_carrays.py
new file mode 100644
index 0000000..e13ef19
--- /dev/null
+++ b/scripts/build/gen_isr_tables_parser_carrays.py
@@ -0,0 +1,292 @@
+#!/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>
+
+typedef void (* ISR)(const void *);
+"""
+
+    source_assembly_header = """
+#ifndef ARCH_IRQ_VECTOR_JUMP_CODE
+#error "ARCH_IRQ_VECTOR_JUMP_CODE not defined"
+#endif
+"""
+
+    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 = self.__parse_intlist(intlist)
+
+    def __read_intlist(self, intlist_data):
+        """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 = self.__config.endian_prefix()
+
+        # Extract header and the rest of the data
+        intlist_header_fmt = prefix + "II"
+        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]
+        intdata = intlist_data[header_sz:]
+
+        # Extract information about interrupts
+        if self.__config.check_64b():
+            intlist_entry_fmt = prefix + "iiQQ"
+        else:
+            intlist_entry_fmt = prefix + "iiII"
+
+        intlist["interrupts"] = [i for i in
+                struct.iter_unpack(intlist_entry_fmt, intdata)]
+
+        self.__log.debug("Configured interrupt routing")
+        self.__log.debug("handler    irq flags param")
+        self.__log.debug("--------------------------")
+
+        for irq in intlist["interrupts"]:
+            self.__log.debug("{0:<10} {1:<3} {2:<3}   {3}".format(
+                hex(irq[2]), irq[0], irq[1], hex(irq[3])))
+
+        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"]
+
+        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, func, param in intlist["interrupts"]:
+            if self.__config.test_isr_direct(flags):
+                if not vt:
+                    self.__log.error("Direct Interrupt %d declared with parameter 0x%x "
+                                     "but no vector table in use"
+                                      % (irq, param))
+                if param != 0:
+                    self.__log.error("Direct irq %d declared, but has non-NULL parameter"
+                                     % irq)
+                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] = func
+            else:
+                # Regular interrupt
+                if not swt:
+                    self.__log.error("Regular Interrupt %d declared with parameter 0x%x "
+                                     "but no SW ISR_TABLE in use"
+                                     % (irq, param))
+
+                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))
+                if self.__config.check_shared_interrupts():
+                    lst = swt[table_index]
+                    if (param, func) in lst:
+                        self.__log.error("Attempting to register the same ISR/arg pair twice.")
+                    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 handler 0x{swt[table_index][0][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].append((param, func))
+
+        return vt, swt, nvec
+
+    def __write_code_irq_vector_table(self, fp):
+        fp.write(self.source_assembly_header)
+
+        fp.write("void __irq_vector_table __attribute__((naked)) _irq_vector_table(void) {\n")
+        for i in range(self.__nv):
+            func = self.__vt[i]
+
+            if func is None:
+                func = self.__config.vt_default_handler
+
+            if isinstance(func, int):
+                func_as_string = self.__config.get_sym_from_addr(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(self, fp):
+        fp.write("uintptr_t __irq_vector_table _irq_vector_table[%d] = {\n" % self.__nv)
+        for i in range(self.__nv):
+            func = self.__vt[i]
+
+            if func is None:
+                func = self.__config.vt_default_handler
+
+            if isinstance(func, int):
+                fp.write("\t{},\n".format(func))
+            else:
+                fp.write("\t((uintptr_t)&{}),\n".format(func))
+
+        fp.write("};\n")
+
+    def __write_shared_table(self, fp):
+        fp.write("struct z_shared_isr_table_entry __shared_sw_isr_table"
+                " z_shared_sw_isr_table[%d] = {\n" % self.__nv)
+
+        for i in range(self.__nv):
+            if self.__swt[i] is None:
+                client_num = 0
+                client_list = None
+            else:
+                client_num = len(self.__swt[i])
+                client_list = self.__swt[i]
+
+            if client_num <= 1:
+                fp.write("\t{ },\n")
+            else:
+                fp.write(f"\t{{ .client_num = {client_num}, .clients = {{ ")
+                for j in range(0, client_num):
+                    routine = client_list[j][1]
+                    arg = client_list[j][0]
+
+                    fp.write(f"{{ .isr = (ISR){ hex(routine) if isinstance(routine, int) else routine }, "
+                            f".arg = (const void *){hex(arg)} }},")
+
+                fp.write(" },\n},\n")
+
+        fp.write("};\n")
+
+    def write_source(self, fp):
+        fp.write(self.source_header)
+
+        if self.__config.check_shared_interrupts():
+            self.__write_shared_table(fp)
+
+        if self.__vt:
+            if self.__config.check_sym("CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_ADDRESS"):
+                self.__write_address_irq_vector_table(fp)
+            elif self.__config.check_sym("CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE"):
+                self.__write_code_irq_vector_table(fp)
+            else:
+                self.__log.error("CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_{ADDRESS,CODE} not set")
+
+        if not self.__swt:
+            return
+
+        fp.write("struct _isr_table_entry __sw_isr_table _sw_isr_table[%d] = {\n"
+                % self.__nv)
+
+        level2_offset = self.__config.get_irq_baseoffset(2)
+        level3_offset = self.__config.get_irq_baseoffset(3)
+
+        for i in range(self.__nv):
+            if len(self.__swt[i]) == 0:
+                # Not used interrupt
+                param = "0x0"
+                func = self.__config.swt_spurious_handler
+            elif len(self.__swt[i]) == 1:
+                # Single interrupt
+                param = "{0:#x}".format(self.__swt[i][0][0])
+                func = self.__swt[i][0][1]
+            else:
+                # Shared interrupt
+                param = "&z_shared_sw_isr_table[{0}]".format(i)
+                func = self.__config.swt_shared_handler
+
+            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}, (ISR){1}}}, /* {2} */\n".format(param, func_as_string, i))
+        fp.write("};\n")
diff --git a/scripts/build/gen_isr_tables_parser_local.py b/scripts/build/gen_isr_tables_parser_local.py
new file mode 100644
index 0000000..91bd4a6
--- /dev/null
+++ b/scripts/build/gen_isr_tables_parser_local.py
@@ -0,0 +1,375 @@
+#!/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)