diff --git a/CMakeLists.txt b/CMakeLists.txt index 97bcd98d0d7..5bb1774d020 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,6 +213,11 @@ endif() # Apply the final optimization flag(s) zephyr_compile_options(${OPTIMIZATION_FLAG}) +if(CONFIG_LTO) + add_compile_options($) + add_link_options($) +endif() + # @Intent: Obtain compiler specific flags related to C++ that are not influenced by kconfig zephyr_compile_options($<$:$>) @@ -803,6 +808,10 @@ target_include_directories(${OFFSETS_LIB} PRIVATE kernel/include ${ARCH_DIR}/${ARCH}/include ) + +# Make sure that LTO will never be enabled when compiling offsets.c +set_source_files_properties(${OFFSETS_C_PATH} PROPERTIES COMPILE_OPTIONS $) + target_link_libraries(${OFFSETS_LIB} zephyr_interface) add_dependencies(zephyr_interface ${SYSCALL_LIST_H_TARGET} @@ -1226,10 +1235,11 @@ if(CONFIG_GEN_ISR_TABLES) # 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 $ --intlist-section .intList --intlist-section intList diff --git a/Kconfig.zephyr b/Kconfig.zephyr index c484898206b..848ce58da7e 100644 --- a/Kconfig.zephyr +++ b/Kconfig.zephyr @@ -417,6 +417,13 @@ config NO_OPTIMIZATIONS default stack sizes in order to avoid stack overflows. endchoice +config LTO + bool "Link Time Optimization [EXPERIMENTAL]" + depends on !(GEN_ISR_TABLES || GEN_IRQ_VECTOR_TABLE) || ISR_TABLES_LOCAL_DECLARATION + select EXPERIMENTAL + help + This option enables Link Time Optimization. + config COMPILER_WARNINGS_AS_ERRORS bool "Treat warnings as errors" help diff --git a/arch/Kconfig b/arch/Kconfig index 5e3b96f414d..5c0ad5f49d9 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -391,6 +391,29 @@ config NOCACHE_MEMORY menu "Interrupt Configuration" +config ISR_TABLES_LOCAL_DECLARATION_SUPPORTED + bool + default y + # Userspace is currently not supported + depends on !USERSPACE + # List of currently supported architectures + depends on ARM || ARM64 + # List of currently supported toolchains + depends on "$(ZEPHYR_TOOLCHAIN_VARIANT)" = "zephyr" || "$(ZEPHYR_TOOLCHAIN_VARIANT)" = "gnuarmemb" + +config ISR_TABLES_LOCAL_DECLARATION + bool "ISR tables created locally and placed by linker [EXPERIMENTAL]" + depends on ISR_TABLES_LOCAL_DECLARATION_SUPPORTED + select EXPERIMENTAL + help + Enable new scheme of interrupt tables generation. + This is totally different generator that would create tables entries locally + where the IRQ_CONNECT macro is called and then use the linker script to position it + in the right place in memory. + The most important advantage of such approach is that the generated interrupt tables + are LTO compatible. + The drawback is that the support on the architecture port is required. + config DYNAMIC_INTERRUPTS bool "Installation of IRQs at runtime" help diff --git a/arch/arm/core/CMakeLists.txt b/arch/arm/core/CMakeLists.txt index 1b4fa58eae5..680e21fdb59 100644 --- a/arch/arm/core/CMakeLists.txt +++ b/arch/arm/core/CMakeLists.txt @@ -34,3 +34,11 @@ else() zephyr_linker_sources(ROM_START SORT_KEY 0x0vectors vector_table.ld) zephyr_linker_sources(ROM_START SORT_KEY 0x1vectors cortex_m/vector_table_pad.ld) endif() + +if(CONFIG_GEN_SW_ISR_TABLE) + if(CONFIG_DYNAMIC_INTERRUPTS) + zephyr_linker_sources(RWDATA swi_tables.ld) + else() + zephyr_linker_sources(RODATA swi_tables.ld) + endif() +endif() diff --git a/arch/arm/core/swi_tables.ld b/arch/arm/core/swi_tables.ld new file mode 100644 index 00000000000..a5dd3eaf652 --- /dev/null +++ b/arch/arm/core/swi_tables.ld @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#if LINKER_ZEPHYR_FINAL && defined(CONFIG_ISR_TABLES_LOCAL_DECLARATION) +INCLUDE isr_tables_swi.ld +#endif diff --git a/arch/arm/core/vector_table.ld b/arch/arm/core/vector_table.ld index 615067dbf74..463e389de9f 100644 --- a/arch/arm/core/vector_table.ld +++ b/arch/arm/core/vector_table.ld @@ -51,6 +51,10 @@ _vector_start = .; KEEP(*(.exc_vector_table)) KEEP(*(".exc_vector_table.*")) +#if LINKER_ZEPHYR_FINAL && defined(CONFIG_ISR_TABLES_LOCAL_DECLARATION) +INCLUDE isr_tables_vt.ld +#else KEEP(*(.vectors)) +#endif _vector_end = .; diff --git a/arch/arm64/core/CMakeLists.txt b/arch/arm64/core/CMakeLists.txt index 1804556c1a7..05e4be8c0ea 100644 --- a/arch/arm64/core/CMakeLists.txt +++ b/arch/arm64/core/CMakeLists.txt @@ -55,3 +55,11 @@ if(CMAKE_C_COMPILER_ID STREQUAL "GNU") endif() add_subdirectory_ifdef(CONFIG_XEN xen) + +if(CONFIG_GEN_SW_ISR_TABLE) + if(CONFIG_DYNAMIC_INTERRUPTS) + zephyr_linker_sources(RWDATA swi_tables.ld) + else() + zephyr_linker_sources(RODATA swi_tables.ld) + endif() +endif() diff --git a/arch/arm64/core/swi_tables.ld b/arch/arm64/core/swi_tables.ld new file mode 100644 index 00000000000..a5dd3eaf652 --- /dev/null +++ b/arch/arm64/core/swi_tables.ld @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#if LINKER_ZEPHYR_FINAL && defined(CONFIG_ISR_TABLES_LOCAL_DECLARATION) +INCLUDE isr_tables_swi.ld +#endif diff --git a/arch/common/CMakeLists.txt b/arch/common/CMakeLists.txt index 1a89ba9c13a..409c378f620 100644 --- a/arch/common/CMakeLists.txt +++ b/arch/common/CMakeLists.txt @@ -39,6 +39,11 @@ zephyr_linker_sources_ifdef(CONFIG_GEN_ISR_TABLES ${ZEPHYR_BASE}/include/zephyr/linker/intlist.ld ) +zephyr_linker_sources_ifdef(CONFIG_ISR_TABLES_LOCAL_DECLARATION + SECTIONS + ${ZEPHYR_BASE}/include/zephyr/linker/isr-local-drop-unused.ld +) + zephyr_linker_sources_ifdef(CONFIG_GEN_IRQ_VECTOR_TABLE ROM_START SORT_KEY 0x0vectors diff --git a/arch/common/isr_tables.c b/arch/common/isr_tables.c index 0311f81f252..050597b7b1d 100644 --- a/arch/common/isr_tables.c +++ b/arch/common/isr_tables.c @@ -15,15 +15,27 @@ 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 * header of the initList section, which is used by gen_isr_tables.py to create * the vector and sw isr tables, */ -Z_GENERIC_SECTION(.irq_info) struct int_list_header _iheader = { +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/arch/common/shared_irq.c b/arch/common/shared_irq.c index 68641cb2bb0..a05e78002ce 100644 --- a/arch/common/shared_irq.c +++ b/arch/common/shared_irq.c @@ -20,7 +20,7 @@ void z_shared_isr(const void *data) { size_t i; const struct z_shared_isr_table_entry *entry; - const struct z_shared_isr_client *client; + const struct _isr_table_entry *client; entry = data; @@ -42,7 +42,7 @@ void z_isr_install(unsigned int irq, void (*routine)(const void *), { struct z_shared_isr_table_entry *shared_entry; struct _isr_table_entry *entry; - struct z_shared_isr_client *client; + struct _isr_table_entry *client; unsigned int table_idx; int i; k_spinlock_key_t key; @@ -103,10 +103,10 @@ void z_isr_install(unsigned int irq, void (*routine)(const void *), k_spin_unlock(&lock, key); } -static void swap_client_data(struct z_shared_isr_client *a, - struct z_shared_isr_client *b) +static void swap_client_data(struct _isr_table_entry *a, + struct _isr_table_entry *b) { - struct z_shared_isr_client tmp; + struct _isr_table_entry tmp; tmp.arg = a->arg; tmp.isr = a->isr; @@ -162,7 +162,7 @@ int z_isr_uninstall(unsigned int irq, { struct z_shared_isr_table_entry *shared_entry; struct _isr_table_entry *entry; - struct z_shared_isr_client *client; + struct _isr_table_entry *client; unsigned int table_idx; size_t i; k_spinlock_key_t key; diff --git a/cmake/bintools/gnu/target.cmake b/cmake/bintools/gnu/target.cmake index 86c66b4825c..612f6de79b2 100644 --- a/cmake/bintools/gnu/target.cmake +++ b/cmake/bintools/gnu/target.cmake @@ -5,8 +5,13 @@ find_program(CMAKE_OBJCOPY ${CROSS_COMPILE}objcopy PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) find_program(CMAKE_OBJDUMP ${CROSS_COMPILE}objdump PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) find_program(CMAKE_AS ${CROSS_COMPILE}as PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) -find_program(CMAKE_AR ${CROSS_COMPILE}ar PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) -find_program(CMAKE_RANLIB ${CROSS_COMPILE}ranlib PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) +if(CONFIG_LTO) + find_program(CMAKE_AR ${CROSS_COMPILE}gcc-ar PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) + find_program(CMAKE_RANLIB ${CROSS_COMPILE}gcc-ranlib PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) +else() + find_program(CMAKE_AR ${CROSS_COMPILE}ar PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) + find_program(CMAKE_RANLIB ${CROSS_COMPILE}ranlib PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) +endif() find_program(CMAKE_READELF ${CROSS_COMPILE}readelf PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) find_program(CMAKE_NM ${CROSS_COMPILE}nm PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) find_program(CMAKE_STRIP ${CROSS_COMPILE}strip PATHS ${TOOLCHAIN_HOME} NO_DEFAULT_PATH) diff --git a/cmake/compiler/gcc/compiler_flags.cmake b/cmake/compiler/gcc/compiler_flags.cmake index 5b1dbde49c6..8e79d32387f 100644 --- a/cmake/compiler/gcc/compiler_flags.cmake +++ b/cmake/compiler/gcc/compiler_flags.cmake @@ -21,6 +21,9 @@ endif() set_compiler_property(PROPERTY optimization_speed -O2) set_compiler_property(PROPERTY optimization_size -Os) +set_compiler_property(PROPERTY optimization_lto -flto) +set_compiler_property(PROPERTY prohibit_lto -fno-lto) + ####################################################### # This section covers flags related to warning levels # ####################################################### diff --git a/cmake/linker/ld/linker_flags.cmake b/cmake/linker/ld/linker_flags.cmake index 8209f4dfbdc..5660b410760 100644 --- a/cmake/linker/ld/linker_flags.cmake +++ b/cmake/linker/ld/linker_flags.cmake @@ -12,6 +12,8 @@ endif() set_property(TARGET linker PROPERTY partial_linking "-r") +set_property(TARGET linker PROPERTY lto_arguments -flto -fno-ipa-sra -ffunction-sections -fdata-sections) + # Some linker flags might not be purely ld specific, but a combination of # linker and compiler, such as: # --coverage for clang diff --git a/doc/kernel/services/interrupts.rst b/doc/kernel/services/interrupts.rst index 849e614a562..7af101a40d1 100644 --- a/doc/kernel/services/interrupts.rst +++ b/doc/kernel/services/interrupts.rst @@ -457,6 +457,38 @@ Interrupt tables are set up at build time using some special build tools. The details laid out here apply to all architectures except x86, which are covered in the `x86 Details`_ section below. +The invocation of :c:macro:`IRQ_CONNECT` will declare an instance of +struct _isr_list wich is placed in a special .intList section. +This section is placed in compiled code on precompilation stages only. +It is meant to be used by Zephyr script to generate interrupt tables +and is removed from the final build. +The script implements different parsers to process the data from .intList section +and produce the required output. + +The default parser generates C arrays filled with arguments and interrupt +handlers in a form of addresses directly taken from .intList section entries. +It works with all the architectures and compillers (with the exception mentioned above). +The limitation of this parser is the fact that after the arrays are generated +it is expected for the code not to relocate. +Any relocation on this stage may lead to the situation where the entry in the interrupt array +is no longer pointing to the function that was expected. +It means that this parser, being more compatible is limiting us from using Link Time Optimization. + +The local isr declaration parser uses different approach to construct +the same arrays at binnary level. +All the entries to the arrays are declared and defined locally, +directly in the file where :c:macro:`IRQ_CONNECT` is used. +They are placed in a section with the unique, synthetized name. +The name of the section is then placed in .intList section and it is used to create linker script +to properly place the created entry in the right place in the memory. +This parser is now limited to the supported architectures and toolchains but in reward it keeps +the information about object relations for linker thus allowing the Link Time Optimization. + +Implementation using C arrays +----------------------------- + +This is the default configuration available for all Zephyr supported architectures. + Any invocation of :c:macro:`IRQ_CONNECT` will declare an instance of struct _isr_list which is placed in a special .intList section: @@ -500,7 +532,7 @@ do not support the notion of interrupt priority, in which case the priority argument is ignored. Vector Table ------------- +~~~~~~~~~~~~ A vector table is generated when :kconfig:option:`CONFIG_GEN_IRQ_VECTOR_TABLE` is enabled. This data structure is used natively by the CPU and is simply an array of function pointers, where each element n corresponds to the IRQ handler @@ -527,7 +559,7 @@ CONFIG_GEN_IRQ_START_VECTOR needs to be set to properly offset the indices in the table. SW ISR Table ------------- +~~~~~~~~~~~~ This is an array of struct _isr_table_entry: .. code-block:: c @@ -542,14 +574,14 @@ argument and execute it. The active IRQ line is looked up in an interrupt controller register and used to index this table. Shared SW ISR Table -------------------- +~~~~~~~~~~~~~~~~~~~ This is an array of struct z_shared_isr_table_entry: .. code-block:: c struct z_shared_isr_table_entry { - struct z_shared_isr_client clients[CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS]; + struct _isr_table_entry clients[CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS]; size_t client_num; }; @@ -558,15 +590,88 @@ lines. Whenever an interrupt line becomes shared, c:func:`z_shared_isr` will replace the currently registered ISR in _sw_isr_table. This special ISR will iterate through the list of registered clients and invoke the ISRs. -The definition for struct z_shared_isr_client is as follows: +Implementation using linker script +---------------------------------- + +This way of prepare and parse .isrList section to implement interrupt vectors arrays +is called local isr declaration. +The name comes from the fact that all the entries to the arrays that would create +interrupt vectors are created locally in place of invocation of :c:macro:`IRQ_CONNECT` macro. +Then automatically generated linker scripts are used to place it in the right place in the memory. + +This option requires enabling by the choose of :kconfig:option:`ISR_TABLES_LOCAL_DECLARATION`. +If this configuration is supported by the used architecture and toolchaing the +:kconfig:option:`ISR_TABLES_LOCAL_DECLARATION_SUPPORTED` is set. +See defails of this option for the information about currently supported configurations. + +Any invocation of :c:macro:`IRQ_CONNECT` or `IRQ_DIRECT_CONNECT` will declare an instance of struct +_isr_list_sname which is placde in a special .intList section: + +.. code-block:: c + + 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[]; + }; + +Note that the section name is placed in flexible array member. +It means that the size of the initialized structure will warry depending on the +structure name length. +The whole entry is used by the script during the build of the application +and has all the information needed for proper interrupt placement. + +Beside of the _isr_list_sname the :c:macro:`IRQ_CONNECT` macro generates an entry +that would be the part of the interrupt array: .. code-block:: c - struct z_shared_isr_client { - void (*isr)(const void *arg); + struct _isr_table_entry { const void *arg; + void (*isr)(const void *); }; +This array is placed in a section with the name saved in _isr_list_sname structure. + +The values created by :c:macro:`IRQ_DIRECT_CONNECT` macro depends on the architecture. +It can be changed to variable that points to a interrupt handler: + +.. code-block:: c + + static uintptr_t = ((uintptr_t)func); + +Or to actuall naked function that implements a jump to the interrupt handler: + +.. code-block:: c + + static void (void) + { + __asm(ARCH_IRQ_VECTOR_JUMP_CODE(func)); + } + +Simillar like for :c:macro:`IRQ_CONNECT`, the created variable or function is placed +in a section, saved in _isr_list_sname section. + +Files generated by the script +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The interrupt tables generator script creates 3 files: +isr_tables.c, isr_tables_swi.ld, and isr_tables_vt.ld. + +The isr_tables.c will contain all the structures for interrupts, direct interrupts and +shared interrupts (if enabled). This file implements only all the structures that +are not implemented by the application, leaving a comment where the interrupt +not implemented here can be found. + +Then two linker files are used. The isr_tables_vt.ld file is included in place +where the interrupt vectors are required to be placed in the selected architecture. +The isr_tables_swi.ld file describes the placement of the software interrupt table +elements. The separated file is required as it might be placed in writable on nonwritable +section, depending on the current configuration. + x86 Details ----------- diff --git a/include/zephyr/arch/arc/v2/irq.h b/include/zephyr/arch/arc/v2/irq.h index e0b42549597..45b9d138857 100644 --- a/include/zephyr/arch/arc/v2/irq.h +++ b/include/zephyr/arch/arc/v2/irq.h @@ -77,7 +77,7 @@ extern void z_irq_priority_set(unsigned int irq, unsigned int prio, */ #define ARCH_IRQ_DIRECT_CONNECT(irq_p, priority_p, isr_p, flags_p) \ { \ - Z_ISR_DECLARE(irq_p, ISR_FLAG_DIRECT, isr_p, NULL); \ + Z_ISR_DECLARE_DIRECT(irq_p, ISR_FLAG_DIRECT, isr_p); \ BUILD_ASSERT(priority_p || !IS_ENABLED(CONFIG_ARC_FIRQ) || \ (IS_ENABLED(CONFIG_ARC_FIRQ_STACK) && \ !IS_ENABLED(CONFIG_ARC_STACK_CHECKING)), \ diff --git a/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld b/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld index a27dd55a8cf..f8b117e9193 100644 --- a/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld +++ b/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld @@ -81,7 +81,7 @@ MEMORY RAM (wx) : ORIGIN = RAM_ADDR, LENGTH = RAM_SIZE LINKER_DT_REGIONS() /* Used by and documented in include/linker/intlist.ld */ - IDT_LIST (wx) : ORIGIN = 0xFFFFF7FF, LENGTH = 2K + IDT_LIST (wx) : ORIGIN = 0xFFFF8000, LENGTH = 32K } ENTRY(CONFIG_KERNEL_ENTRY) diff --git a/include/zephyr/arch/arm/cortex_m/scripts/linker.ld b/include/zephyr/arch/arm/cortex_m/scripts/linker.ld index 5050c778627..cbe4a2781e4 100644 --- a/include/zephyr/arch/arm/cortex_m/scripts/linker.ld +++ b/include/zephyr/arch/arm/cortex_m/scripts/linker.ld @@ -132,7 +132,7 @@ MEMORY #endif LINKER_DT_REGIONS() /* Used by and documented in include/linker/intlist.ld */ - IDT_LIST (wx) : ORIGIN = 0xFFFFF7FF, LENGTH = 2K + IDT_LIST (wx) : ORIGIN = 0xFFFF7FFF, LENGTH = 32K } ENTRY(CONFIG_KERNEL_ENTRY) diff --git a/include/zephyr/arch/arm/irq.h b/include/zephyr/arch/arm/irq.h index 357c91a83ae..143b8e54179 100644 --- a/include/zephyr/arch/arm/irq.h +++ b/include/zephyr/arch/arm/irq.h @@ -127,7 +127,7 @@ extern void z_arm_interrupt_init(void); BUILD_ASSERT(IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS) || !(flags_p & IRQ_ZERO_LATENCY), \ "ZLI interrupt registered but feature is disabled"); \ _CHECK_PRIO(priority_p, flags_p) \ - Z_ISR_DECLARE(irq_p, ISR_FLAG_DIRECT, isr_p, NULL); \ + Z_ISR_DECLARE_DIRECT(irq_p, ISR_FLAG_DIRECT, isr_p); \ z_arm_irq_priority_set(irq_p, priority_p, flags_p); \ } diff --git a/include/zephyr/arch/arm64/scripts/linker.ld b/include/zephyr/arch/arm64/scripts/linker.ld index fa08b730304..ad7832f16ec 100644 --- a/include/zephyr/arch/arm64/scripts/linker.ld +++ b/include/zephyr/arch/arm64/scripts/linker.ld @@ -61,7 +61,7 @@ MEMORY FLASH (rx) : ORIGIN = ROM_ADDR, LENGTH = ROM_SIZE RAM (wx) : ORIGIN = RAM_ADDR, LENGTH = RAM_SIZE /* Used by and documented in include/linker/intlist.ld */ - IDT_LIST (wx) : ORIGIN = 0xFFFFF7FF, LENGTH = 2K + IDT_LIST (wx) : ORIGIN = 0xFFFF8000, LENGTH = 32K } ENTRY(CONFIG_KERNEL_ENTRY) @@ -105,8 +105,11 @@ SECTIONS KEEP(*(.exc_vector_table)) KEEP(*(".exc_vector_table.*")) +#if LINKER_ZEPHYR_FINAL && defined(CONFIG_ISR_TABLES_LOCAL_DECLARATION) + INCLUDE isr_tables_vt.ld +#else KEEP(*(.vectors)) - +#endif _vector_end = .; *(.text) diff --git a/include/zephyr/arch/riscv/irq.h b/include/zephyr/arch/riscv/irq.h index fa4b3989f05..d067ccfca23 100644 --- a/include/zephyr/arch/riscv/irq.h +++ b/include/zephyr/arch/riscv/irq.h @@ -69,8 +69,8 @@ extern void z_riscv_irq_priority_set(unsigned int irq, #define ARCH_IRQ_DIRECT_CONNECT(irq_p, priority_p, isr_p, flags_p) \ { \ - Z_ISR_DECLARE(irq_p + CONFIG_RISCV_RESERVED_IRQ_ISR_TABLES_OFFSET, \ - ISR_FLAG_DIRECT, isr_p, NULL); \ + Z_ISR_DECLARE_DIRECT(irq_p + CONFIG_RISCV_RESERVED_IRQ_ISR_TABLES_OFFSET, \ + ISR_FLAG_DIRECT, isr_p); \ } #define ARCH_ISR_DIRECT_HEADER() arch_isr_direct_header() diff --git a/include/zephyr/linker/irq-vector-table-section.ld b/include/zephyr/linker/irq-vector-table-section.ld index 17c483db98f..141eee4d28d 100644 --- a/include/zephyr/linker/irq-vector-table-section.ld +++ b/include/zephyr/linker/irq-vector-table-section.ld @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: Apache-2.0 */ +#if !(LINKER_ZEPHYR_FINAL && CONFIG_ISR_TABLES_LOCAL_DECLARATION) . = ALIGN(CONFIG_ARCH_IRQ_VECTOR_TABLE_ALIGN); KEEP(*(_IRQ_VECTOR_TABLE_SECTION_SYMS)) +#endif /* * Some ARM platforms require this symbol to be placed after the IRQ vector diff --git a/include/zephyr/linker/isr-local-drop-unused.ld b/include/zephyr/linker/isr-local-drop-unused.ld new file mode 100644 index 00000000000..9b6e1272413 --- /dev/null +++ b/include/zephyr/linker/isr-local-drop-unused.ld @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#if LINKER_ZEPHYR_FINAL && CONFIG_ISR_TABLES_LOCAL_DECLARATION +/DISCARD/ : +{ + KEEP(*(.vectors)) + KEEP(*(_IRQ_VECTOR_TABLE_SECTION_SYMS)) +} +#endif diff --git a/include/zephyr/sw_isr_table.h b/include/zephyr/sw_isr_table.h index f43efafad49..7b1bfddb2cb 100644 --- a/include/zephyr/sw_isr_table.h +++ b/include/zephyr/sw_isr_table.h @@ -18,6 +18,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -56,6 +57,9 @@ struct _irq_parent_entry { * uses it to create the IRQ vector table and the _sw_isr_table. * * More discussion in include/linker/intlist.ld + * + * This is a version used when CONFIG_ISR_TABLES_LOCAL_DECLARATION is disabled. + * See _isr_list_sname used otherwise. */ struct _isr_list { /** IRQ line number */ @@ -68,14 +72,27 @@ struct _isr_list { const void *param; }; -#ifdef CONFIG_SHARED_INTERRUPTS -struct z_shared_isr_client { - void (*isr)(const void *arg); - const void *arg; +/* + * Data structure created in a special binary .intlist section for each + * configured interrupt. gen_isr_tables.py pulls this out of the binary and + * uses it to create linker script chunks that would place interrupt table entries + * in the right place in the memory. + * + * This is a version used when CONFIG_ISR_TABLES_LOCAL_DECLARATION is enabled. + * See _isr_list used otherwise. + */ +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[]; }; +#ifdef CONFIG_SHARED_INTERRUPTS struct z_shared_isr_table_entry { - struct z_shared_isr_client clients[CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS]; + struct _isr_table_entry clients[CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS]; size_t client_num; }; @@ -90,6 +107,90 @@ extern struct z_shared_isr_table_entry z_shared_sw_isr_table[]; #define _MK_ISR_NAME(x, y) __MK_ISR_NAME(x, y) #define __MK_ISR_NAME(x, y) __isr_ ## x ## _irq_ ## y + +#if IS_ENABLED(CONFIG_ISR_TABLES_LOCAL_DECLARATION) + +#define _MK_ISR_ELEMENT_NAME(func, id) __MK_ISR_ELEMENT_NAME(func, id) +#define __MK_ISR_ELEMENT_NAME(func, id) __isr_table_entry_ ## func ## _irq_ ## id + +#define _MK_IRQ_ELEMENT_NAME(func, id) __MK_ISR_ELEMENT_NAME(func, id) +#define __MK_IRQ_ELEMENT_NAME(func, id) __irq_table_entry_ ## func ## _irq_ ## id + +#define _MK_ISR_SECTION_NAME(prefix, file, counter) \ + "." Z_STRINGIFY(prefix)"."file"." Z_STRINGIFY(counter) + +#define _MK_ISR_ELEMENT_SECTION(counter) _MK_ISR_SECTION_NAME(irq, __FILE__, counter) +#define _MK_IRQ_ELEMENT_SECTION(counter) _MK_ISR_SECTION_NAME(isr, __FILE__, counter) + +/* Separated macro to create ISR table entry only. + * Used by Z_ISR_DECLARE and ISR tables generation script. + */ +#define _Z_ISR_TABLE_ENTRY(irq, func, param, sect) \ + static Z_DECL_ALIGN(struct _isr_table_entry) \ + __attribute__((section(sect))) \ + __used _MK_ISR_ELEMENT_NAME(func, __COUNTER__) = { \ + .arg = (const void *)(param), \ + .isr = (void (*)(const void *))(void *)(func) \ + } + +#define Z_ISR_DECLARE_C(irq, flags, func, param, counter) \ + _Z_ISR_DECLARE_C(irq, flags, func, param, counter) + +#define _Z_ISR_DECLARE_C(irq, flags, func, param, counter) \ + _Z_ISR_TABLE_ENTRY(irq, func, param, _MK_ISR_ELEMENT_SECTION(counter)); \ + static struct _isr_list_sname Z_GENERIC_SECTION(.intList) \ + __used _MK_ISR_NAME(func, counter) = \ + {irq, flags, _MK_ISR_ELEMENT_SECTION(counter)} + +/* Create an entry for _isr_table to be then placed by the linker. + * An instance of struct _isr_list which gets put in the .intList + * section is created with the name of the section where _isr_table entry is placed to be then + * used by isr generation script to create linker script chunk. + */ +#define Z_ISR_DECLARE(irq, flags, func, param) \ + BUILD_ASSERT(((flags) & ISR_FLAG_DIRECT) == 0, "Use Z_ISR_DECLARE_DIRECT macro"); \ + Z_ISR_DECLARE_C(irq, flags, func, param, __COUNTER__) + + +/* Separated macro to create ISR Direct table entry only. + * Used by Z_ISR_DECLARE_DIRECT and ISR tables generation script. + */ +#define _Z_ISR_DIRECT_TABLE_ENTRY(irq, func, sect) \ + COND_CODE_1(CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_ADDRESS, ( \ + static Z_DECL_ALIGN(uintptr_t) \ + __attribute__((section(sect))) \ + __used _MK_IRQ_ELEMENT_NAME(func, __COUNTER__) = ((uintptr_t)(func)); \ + ), ( \ + static void __attribute__((section(sect))) __attribute__((naked)) \ + __used _MK_IRQ_ELEMENT_NAME(func, __COUNTER__)(void) { \ + __asm(ARCH_IRQ_VECTOR_JUMP_CODE(func)); \ + } \ + )) + +#define Z_ISR_DECLARE_DIRECT_C(irq, flags, func, counter) \ + _Z_ISR_DECLARE_DIRECT_C(irq, flags, func, counter) + +#define _Z_ISR_DECLARE_DIRECT_C(irq, flags, func, counter) \ + _Z_ISR_DIRECT_TABLE_ENTRY(irq, func, _MK_IRQ_ELEMENT_SECTION(counter)); \ + static struct _isr_list_sname Z_GENERIC_SECTION(.intList) \ + __used _MK_ISR_NAME(func, counter) = { \ + irq, \ + ISR_FLAG_DIRECT | (flags), \ + _MK_IRQ_ELEMENT_SECTION(counter)} + +/* Create an entry to irq table and place it in specific section which name is then placed + * in an instance of struct _isr_list to be then used by the isr generation script to create + * the linker script chunks. + */ +#define Z_ISR_DECLARE_DIRECT(irq, flags, func) \ + BUILD_ASSERT(IS_ENABLED(CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_ADDRESS) || \ + IS_ENABLED(CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE), \ + "CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_{ADDRESS,CODE} not set"); \ + Z_ISR_DECLARE_DIRECT_C(irq, flags, func, __COUNTER__) + + +#else /* IS_ENABLED(CONFIG_ISR_TABLES_LOCAL_DECLARATION) */ + /* Create an instance of struct _isr_list which gets put in the .intList * section. This gets consumed by gen_isr_tables.py which creates the vector * and/or SW ISR tables. @@ -99,6 +200,14 @@ extern struct z_shared_isr_table_entry z_shared_sw_isr_table[]; __used _MK_ISR_NAME(func, __COUNTER__) = \ {irq, flags, (void *)&func, (const void *)param} +/* The version of the Z_ISR_DECLARE that should be used for direct ISR declaration. + * It is here for the API match the version with CONFIG_ISR_TABLES_LOCAL_DECLARATION enabled. + */ +#define Z_ISR_DECLARE_DIRECT(irq, flags, func) \ + Z_ISR_DECLARE(irq, ISR_FLAG_DIRECT | (flags), func, NULL) + +#endif + #define IRQ_TABLE_SIZE (CONFIG_NUM_IRQS - CONFIG_GEN_IRQ_START_VECTOR) #ifdef CONFIG_DYNAMIC_INTERRUPTS diff --git a/scripts/build/gen_isr_tables.py b/scripts/build/gen_isr_tables.py index cd329f85551..84812d708e8 100755 --- a/scripts/build/gen_isr_tables.py +++ b/scripts/build/gen_isr_tables.py @@ -2,118 +2,280 @@ # # 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 struct import sys import os +import importlib 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 - -INTERRUPT_BITS = [8, 8, 8] - -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(elfobj, syms, snames): - """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_sect = None - intlist = {} - prefix = endian_prefix() +class gen_isr_log: - intlist_header_fmt = prefix + "II" - if "CONFIG_64BIT" in syms: - intlist_entry_fmt = prefix + "iiQQ" - else: - intlist_entry_fmt = prefix + "iiII" + def __init__(self, debug = False): + self.__debug = debug - for sname in snames: - intList_sect = elfobj.get_section_by_name(sname) - if intList_sect is not None: - debug("Found intlist section: \"{}\"".format(sname)) - break + def debug(self, text): + """Print debug message if debugging is enabled. - if intList_sect is None: - error("Cannot find the intlist section!") + Note - this function requires config global variable to be initialized. + """ + if self.__debug: + sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") - intdata = intList_sect.data() + @staticmethod + def error(text): + sys.exit(os.path.basename(sys.argv[0]) + ": error: " + text + "\n") - header_sz = struct.calcsize(intlist_header_fmt) - header = struct.unpack_from(intlist_header_fmt, intdata, 0) - intdata = intdata[header_sz:] + def set_debug(self, state): + self.__debug = state - debug(str(header)) - intlist["num_vectors"] = header[0] - intlist["offset"] = header[1] +log = gen_isr_log() - intlist["interrupts"] = [i for i in - struct.iter_unpack(intlist_entry_fmt, intdata)] - debug("Configured interrupt routing") - debug("handler irq flags param") - debug("--------------------------") +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, 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") - 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 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 parse_args(): - global args +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) @@ -124,6 +286,12 @@ def parse_args(): 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", @@ -134,313 +302,43 @@ def parse_args(): help="The name of the section to search for the interrupt data. " "This is accumulative argument. The first section found would be used.") - 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_shared_table(fp, shared, nv): - fp.write("struct z_shared_isr_table_entry __shared_sw_isr_table" - " z_shared_sw_isr_table[%d] = {\n" % nv) - - for i in range(nv): - client_num = shared[i][1] - client_list = shared[i][0] - - if not client_num: - 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_file(fp, vt, swt, intlist, syms, shared): - fp.write(source_header) - - nv = intlist["num_vectors"] - - if "CONFIG_SHARED_INTERRUPTS" in syms: - write_shared_table(fp, shared, nv) - - 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 = "{0:#x}".format(swt[i][0]) - func = swt[i][1] - - if isinstance (func, str) and "z_shared_isr" in func: - param = "&z_shared_sw_isr_table[{0}]".format(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}, (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 bit_mask(bits): - mask = 0 - for _ in range(0, bits): - mask = (mask << 1) | 1 - return mask - -def update_masks(): - global FIRST_LVL_INTERRUPTS - global SECND_LVL_INTERRUPTS - global THIRD_LVL_INTERRUPTS - - if sum(INTERRUPT_BITS) > 32: - raise ValueError("Too many interrupt bits") - - FIRST_LVL_INTERRUPTS = bit_mask(INTERRUPT_BITS[0]) - SECND_LVL_INTERRUPTS = bit_mask(INTERRUPT_BITS[1]) << INTERRUPT_BITS[0] - THIRD_LVL_INTERRUPTS = bit_mask(INTERRUPT_BITS[2]) << INTERRUPT_BITS[0] + INTERRUPT_BITS[2] + return parser.parse_args() def main(): - parse_args() + args = parse_args() + # Configure logging as soon as possible + log.set_debug(args.debug) with open(args.kernel, "rb") as fp: kernel = ELFFile(fp) - syms = get_symbols(kernel) - intlist = read_intlist(kernel, syms, args.intlist_section) - - if "CONFIG_MULTI_LEVEL_INTERRUPTS" in syms: - max_irq_per = syms["CONFIG_MAX_IRQ_PER_AGGREGATOR"] - - INTERRUPT_BITS[0] = syms["CONFIG_1ST_LEVEL_INTERRUPT_BITS"] - INTERRUPT_BITS[1] = syms["CONFIG_2ND_LEVEL_INTERRUPT_BITS"] - INTERRUPT_BITS[2] = syms["CONFIG_3RD_LEVEL_INTERRUPT_BITS"] - update_masks() - - 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)) - - 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)" - swt_shared_handler = "((uintptr_t)&z_shared_isr)" - 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)] - shared = [([], 0) 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 + 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"): + 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: - # 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) >> INTERRUPT_BITS[0] + INTERRUPT_BITS[1] - irq2 = (irq & SECND_LVL_INTERRUPTS) >> INTERRUPT_BITS[0] - 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 "CONFIG_SHARED_INTERRUPTS" in syms: - if swt[table_index] != (0, swt_spurious_handler): - # check client limit - if syms["CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS"] == shared[table_index][1]: - error(f"Reached shared interrupt client limit. Maybe increase" - + f" CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS?") - lst = shared[table_index][0] - delta_size = 1 - if not shared[table_index][1]: - lst.append(swt[table_index]) - # note: the argument will be fixed when writing the ISR table - # to isr_table.c - swt[table_index] = (0, swt_shared_handler) - delta_size += 1 - if (param, func) in lst: - error("Attempting to register the same ISR/arg pair twice.") - lst.append((param, func)) - shared[table_index] = (lst, shared[table_index][1] + delta_size) - else: - swt[table_index] = (param, func) - else: - 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?" - ) - else: - swt[table_index] = (param, func) + 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: - write_source_file(fp, vt, swt, intlist, syms, shared) + 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 00000000000..e13ef19c1f8 --- /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 +#include +#include +#include + +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 00000000000..91bd4a65471 --- /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 +#include +#include +#include + +""" + + 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) diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll.c index db8f25a374c..cde5f5befc9 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll.c @@ -191,8 +191,13 @@ int lll_init(void) radio_nrf5_isr, IRQ_CONNECT_FLAGS); IRQ_CONNECT(RTC0_IRQn, CONFIG_BT_CTLR_ULL_HIGH_PRIO, rtc0_nrf5_isr, NULL, 0); +#if defined(CONFIG_BT_CTLR_ZLI) + IRQ_DIRECT_CONNECT(HAL_SWI_RADIO_IRQ, CONFIG_BT_CTLR_LLL_PRIO, + swi_lll_nrf5_isr, IRQ_CONNECT_FLAGS); +#else IRQ_CONNECT(HAL_SWI_RADIO_IRQ, CONFIG_BT_CTLR_LLL_PRIO, swi_lll_nrf5_isr, NULL, IRQ_CONNECT_FLAGS); +#endif #if defined(CONFIG_BT_CTLR_LOW_LAT) || \ (CONFIG_BT_CTLR_ULL_HIGH_PRIO != CONFIG_BT_CTLR_ULL_LOW_PRIO) IRQ_CONNECT(HAL_SWI_JOB_IRQ, CONFIG_BT_CTLR_ULL_LOW_PRIO, diff --git a/tests/kernel/common/testcase.yaml b/tests/kernel/common/testcase.yaml index 721d4bb9c3d..ae9c39c8fb5 100644 --- a/tests/kernel/common/testcase.yaml +++ b/tests/kernel/common/testcase.yaml @@ -43,3 +43,10 @@ tests: tags: picolibc extra_configs: - CONFIG_PICOLIBC=y + kernel.common.lto: + filter: CONFIG_ISR_TABLES_LOCAL_DECLARATION_SUPPORTED + tags: lto + extra_configs: + - CONFIG_TEST_USERSPACE=n + - CONFIG_ISR_TABLES_LOCAL_DECLARATION=y + - CONFIG_LTO=y diff --git a/tests/kernel/gen_isr_table/testcase.yaml b/tests/kernel/gen_isr_table/testcase.yaml index 4ee3ad00ac9..eb8babe3635 100644 --- a/tests/kernel/gen_isr_table/testcase.yaml +++ b/tests/kernel/gen_isr_table/testcase.yaml @@ -4,12 +4,12 @@ common: - interrupt - isr_table tests: - arch.interrupt.gen_isr_table.arm_baseline: + arch.interrupt.gen_isr_table.arm_baseline: &arm-baseline platform_allow: qemu_cortex_m3 filter: CONFIG_GEN_ISR_TABLES and CONFIG_ARMV6_M_ARMV8_M_BASELINE extra_configs: - CONFIG_NULL_POINTER_EXCEPTION_DETECTION_NONE=y - arch.interrupt.gen_isr_table.arm_baseline.linker_generator: + arch.interrupt.gen_isr_table.arm_baseline.linker_generator: &arm-baseline-linker-generator platform_allow: qemu_cortex_m3 filter: CONFIG_GEN_ISR_TABLES and CONFIG_ARMV6_M_ARMV8_M_BASELINE tags: @@ -17,7 +17,7 @@ tests: extra_configs: - CONFIG_NULL_POINTER_EXCEPTION_DETECTION_NONE=y - CONFIG_CMAKE_LINKER_GENERATOR=y - arch.interrupt.gen_isr_table.arm_mainline: + arch.interrupt.gen_isr_table.arm_mainline: &arm-mainline platform_allow: qemu_cortex_m3 platform_exclude: - stmf103_mini @@ -40,6 +40,27 @@ tests: - CONFIG_GEN_ISR_TABLES=n - CONFIG_NULL_POINTER_EXCEPTION_DETECTION_NONE=y build_only: true + + arch.interrupt.gen_isr_table_local.arm_baseline: + <<: *arm-baseline + filter: CONFIG_GEN_ISR_TABLES and CONFIG_ARMV6_M_ARMV8_M_BASELINE and not CONFIG_USERSPACE + extra_configs: + - CONFIG_NULL_POINTER_EXCEPTION_DETECTION_NONE=y + - CONFIG_ISR_TABLES_LOCAL_DECLARATION=y + arch.interrupt.gen_isr_table_local.arm_baseline.linker_generator: + <<: *arm-baseline-linker-generator + filter: CONFIG_GEN_ISR_TABLES and CONFIG_ARMV6_M_ARMV8_M_BASELINE and not CONFIG_USERSPACE + extra_configs: + - CONFIG_NULL_POINTER_EXCEPTION_DETECTION_NONE=y + - CONFIG_CMAKE_LINKER_GENERATOR=y + - CONFIG_ISR_TABLES_LOCAL_DECLARATION=y + arch.interrupt.gen_isr_table_local.arm_mainline: + <<: *arm-mainline + filter: CONFIG_GEN_ISR_TABLES and CONFIG_ARMV6_M_ARMV8_M_BASELINE and not CONFIG_USERSPACE + extra_configs: + - CONFIG_NULL_POINTER_EXCEPTION_DETECTION_NONE=y + - CONFIG_ISR_TABLES_LOCAL_DECLARATION=y + arch.interrupt.gen_isr_table.arc: arch_allow: arc filter: CONFIG_RGF_NUM_BANKS > 1 diff --git a/tests/kernel/interrupt/src/test_shared_irq.h b/tests/kernel/interrupt/src/test_shared_irq.h index fd3c4c4a624..5ddd50a16e1 100644 --- a/tests/kernel/interrupt/src/test_shared_irq.h +++ b/tests/kernel/interrupt/src/test_shared_irq.h @@ -41,7 +41,7 @@ static inline bool client_exists_at_index(void (*routine)(const void *arg), { size_t i; struct z_shared_isr_table_entry *shared_entry; - struct z_shared_isr_client *client; + struct _isr_table_entry *client; shared_entry = &z_shared_sw_isr_table[irq]; diff --git a/tests/kernel/interrupt/testcase.yaml b/tests/kernel/interrupt/testcase.yaml index cbe58c65e47..47cbba93e0d 100644 --- a/tests/kernel/interrupt/testcase.yaml +++ b/tests/kernel/interrupt/testcase.yaml @@ -52,3 +52,23 @@ tests: extra_configs: - CONFIG_SHARED_INTERRUPTS=y filter: not CONFIG_TRUSTED_EXECUTION_NONSECURE + arch.shared_interrupt.lto: + # excluded because of failures during test_prevent_interruption + platform_exclude: qemu_cortex_m0 + arch_exclude: + # same as above, #22956 + - nios2 + # test needs 2 working interrupt lines + - xtensa + # TODO: make test work on this arch + - mips + tags: + - kernel + - interrupt + - lto + extra_configs: + - CONFIG_SHARED_INTERRUPTS=y + - CONFIG_TEST_USERSPACE=n + - CONFIG_ISR_TABLES_LOCAL_DECLARATION=y + - CONFIG_LTO=y + filter: not CONFIG_TRUSTED_EXECUTION_NONSECURE and CONFIG_ISR_TABLES_LOCAL_DECLARATION_SUPPORTED