From 1927b3d02038b9f82f04530e613db007aec1a409 Mon Sep 17 00:00:00 2001 From: Andrew Boie Date: Wed, 8 Feb 2017 17:16:29 -0800 Subject: [PATCH] gen_isr_tables: New static interrupt build mechanism This is a new mechanism for generating interrupt tables which will be useful on many architectures. It replaces the old linker-based mechanism for creating these tables and has a couple advantages: 1) It is now possible to use enums as the IRQ line argument to IRQ_CONNECT(), which should ease CMSIS integration. 2) The vector table itself is now generated, which lets us place interrupts directly into the vector table without having to hard-code them. This is a feature we have long enjoyed on x86 and will enable 'direct' interrupts. 3) More code is common, requiring less arch-specific code to support. This patch introduces the common code for this mechanism. Follow-up patches will enable it on various arches. Issue: ZEP-1038, ZEP-1165 Change-Id: I9acd6e0de8b438fa9293f2e00563628f7510168a Signed-off-by: Andrew Boie --- Makefile | 3 + arch/Kconfig | 48 +++++++ arch/Makefile | 2 +- arch/common/Makefile | 1 + arch/common/Makefile.gen_isr_tables | 68 ++++++++++ arch/common/gen_isr_tables.py | 190 ++++++++++++++++++++++++++++ arch/common/isr_tables.c | 52 ++++++++ include/linker/intlist.ld | 53 ++++++++ include/section_tags.h | 1 + include/sections.h | 4 +- include/sw_isr_table.h | 37 ++++++ 11 files changed, 457 insertions(+), 2 deletions(-) create mode 100644 arch/common/Makefile create mode 100644 arch/common/Makefile.gen_isr_tables create mode 100755 arch/common/gen_isr_tables.py create mode 100644 arch/common/isr_tables.c create mode 100644 include/linker/intlist.ld diff --git a/Makefile b/Makefile index e67a1e1c4aa..0cf5e9036fb 100644 --- a/Makefile +++ b/Makefile @@ -867,6 +867,9 @@ WARN_ABOUT_DEPRECATION := $(if $(CONFIG_BOARD_DEPRECATED),echo -e \ ifeq ($(ARCH),x86) # X86 with its IDT has very special handling for interrupt tables include $(srctree)/arch/x86/Makefile.idt +else ifeq ($(CONFIG_GEN_ISR_TABLES),y) +# Logic for interrupt tables created by scripts/gen_isr_tables.py +include $(srctree)/arch/common/Makefile.gen_isr_tables else # Otherwise, nothing to do, prebuilt kernel is the real one $(KERNEL_ELF_NAME): $(PREBUILT_KERNEL) diff --git a/arch/Kconfig b/arch/Kconfig index 4f6b08a2f91..5983765079a 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -110,6 +110,54 @@ config BOARD if not found we look for the linker file in arch//soc// +# +# Interrupt related configs +# + +config GEN_ISR_TABLES + bool + prompt "Use generated IRQ tables" + default n + help + This option controls whether a platform uses the gen_isr_tables + script to generate its interrupt tables. This mechanism will create + an appropriate hardware vector table and/or software IRQ table. + +config GEN_IRQ_VECTOR_TABLE + bool + prompt "Generate an interrupt vector table" + default y + depends on GEN_ISR_TABLES + help + This option controls whether a platform using gen_isr_tables + needs an interrupt vector table created. Only disable this if the + platform does not use a vector table at all, or requires the vector + table to be in a format that is not an array of function pointers + indexed by IRQ line. In the latter case, the vector table must be + supplied by the application or architecture code. + +config GEN_SW_ISR_TABLE + bool + prompt "Generate a software ISR table" + default y + depends on GEN_ISR_TABLES + help + This option controls whether a platform using gen_isr_tables + needs a software ISR table table created. This is an array of struct + _isr_table_entry containing the interrupt service routine and supplied + parameter. + +config GEN_IRQ_START_VECTOR + int + prompt "Starting vector for user-configurable interrupts" + default 0 + depends on GEN_ISR_TABLES + help + On some architectures, part of the vector table may be reserved for + system exceptions and is declared separately from the tables + created by gen_isr_tables.py. When creating these tables, this value + will be subtracted from CONFIG_NUM_IRQS to properly size them. + source "arch/*/Kconfig" source "boards/Kconfig" diff --git a/arch/Makefile b/arch/Makefile index ad3a7486e83..636ec368574 100644 --- a/arch/Makefile +++ b/arch/Makefile @@ -1 +1 @@ -obj-y += $(ARCH)/ +obj-y += common/ $(ARCH)/ diff --git a/arch/common/Makefile b/arch/common/Makefile new file mode 100644 index 00000000000..3c1b4d6475b --- /dev/null +++ b/arch/common/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_GEN_ISR_TABLES) += isr_tables.o diff --git a/arch/common/Makefile.gen_isr_tables b/arch/common/Makefile.gen_isr_tables new file mode 100644 index 00000000000..0f51fa6c609 --- /dev/null +++ b/arch/common/Makefile.gen_isr_tables @@ -0,0 +1,68 @@ +GEN_ISR_TABLE := $(srctree)/arch/common/gen_isr_tables.py +OUTPUT_SRC := isr_tables.c +OUTPUT_OBJ := isr_tables.o + +ifeq ($(ARCH),arm) +OUTPUT_FORMAT := elf32-littlearm +OUTPUT_ARCH := arm +else +$(error Output formats not defined for this architecture) +endif + +GEN_ISR_TABLE_EXTRA_ARGS := + +ifeq ($(KBUILD_VERBOSE),1) +GEN_ISR_TABLE_EXTRA_ARGS += --debug +endif + +ifeq ($(CONFIG_GEN_SW_ISR_TABLE),y) +GEN_ISR_TABLE_EXTRA_ARGS += --sw-isr-table +endif + +ifeq ($(CONFIG_GEN_IRQ_VECTOR_TABLE),y) +GEN_ISR_TABLE_EXTRA_ARGS += --vector-table +endif + +# Rule to extract the .intList section from the $(PREBUILT_KERNEL) binary +# and create the source file $(OUTPUT_SRC). This is a C file which contains +# the interrupt tables. +quiet_cmd_gen_irq = IRQ $@ + cmd_gen_irq = \ +( \ + $(OBJCOPY) -I $(OUTPUT_FORMAT) -O binary --only-section=.intList \ + $< isrList.bin && \ + $(GEN_ISR_TABLE) --output-source $@ \ + --intlist isrList.bin $(GEN_ISR_TABLE_EXTRA_ARGS) \ +) + +$(OUTPUT_SRC): $(PREBUILT_KERNEL) $(GEN_ISR_TABLE) + $(call cmd,gen_irq) + +# Build system pattern rules will handle building $(OUTPUT_OBJ) from +# $(OUTPUT_SRC), nothing we need to do here explicitly for its compilation. + +# Now link the kernel again, this time with the compiled interrupt tables +# included, replacing the dummy tables defined in arch/common/isr_tables.c +# +# On x86, we just strip out the intList with objcopy -j. However this is not +# very portable; for instance on ARM this results in a zero-sized program +# header segment which produces a linker warning and gives QEMU fits. +# Set to NOLOAD instead, now that we have extracted the information we need +# from it. +quiet_cmd_lnk_elf = LINK $@ + cmd_lnk_elf = \ +( \ + $(CC) -T linker.cmd $(OUTPUT_OBJ) @$(KERNEL_NAME).lnk \ + -o elf.tmp && \ + $(OBJCOPY) -I $(OUTPUT_FORMAT) -O $(OUTPUT_FORMAT) \ + --set-section-flags .intList=noload \ + elf.tmp $@ && \ + rm -f elf.tmp; \ +) + +$(KERNEL_ELF_NAME): $(OUTPUT_OBJ) linker.cmd + $(call cmd,lnk_elf) + @$(srctree)/scripts/check_link_map.py $(KERNEL_NAME).map + @$(WARN_ABOUT_ASSERT) + @$(WARN_ABOUT_DEPRECATION) + diff --git a/arch/common/gen_isr_tables.py b/arch/common/gen_isr_tables.py new file mode 100755 index 00000000000..754d78432de --- /dev/null +++ b/arch/common/gen_isr_tables.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2017 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +import argparse +import struct +import sys +import os + +ISR_FLAG_DIRECT = (1 << 0) + +def debug(text): + if not args.debug: + return + sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") + +def error(text): + sys.stderr.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") + raise Exception() + +def endian_prefix(): + if args.big_endian: + return ">" + else: + return "<" + +def read_intlist(intlist_path): + """read a binary file containing the contents of the kernel's .intList + section. This is an instance of a header created by + include/linker/intlist.ld: + + struct { + void * spurious_irq_handler; + void * sw_irq_handler; + uint32_t num_isrs; + uint32_t num_vectors; <- typically CONFIG_NUM_IRQS + struct _isr_list isrs[]; <- of size num_isrs + } + + 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 */ + void *param; + }; + """ + + intlist = {} + + prefix = endian_prefix() + + intlist_header_fmt = prefix + "IIII" + intlist_entry_fmt = prefix + "iiII" + + with open(intlist_path, "rb") as fp: + intdata = fp.read() + + header_sz = struct.calcsize(intlist_header_fmt) + header = struct.unpack_from(intlist_header_fmt, intdata, 0) + intdata = intdata[header_sz:] + + debug(str(header)) + + intlist["spurious_handler"] = header[0] + intlist["sw_irq_handler"] = header[1] + intlist["num_isrs"] = header[2] + intlist["num_vectors"] = header[3] + + debug("spurious handler: %s" % hex(header[0])) + + intlist["interrupts"] = [i for i in + struct.iter_unpack(intlist_entry_fmt, intdata)] + + debug("Configured interrupt routing") + debug("handler irq flags param") + debug("--------------------------") + + for irq in intlist["interrupts"]: + debug("{0:<10} {1:<3} {2:<3} {3}".format( + hex(irq[2]), irq[0], irq[1], hex(irq[3]))) + + return intlist + + +def parse_args(): + global args + + parser = argparse.ArgumentParser(description = __doc__, + formatter_class = argparse.RawDescriptionHelpFormatter) + + parser.add_argument("-e", "--big-endian", action="store_true", + help="Target encodes data in big-endian format (little endian is " + "the default)") + parser.add_argument("-d", "--debug", action="store_true", + help="Print additional debugging information") + parser.add_argument("-o", "--output-source", required=True, + help="Output source file") + parser.add_argument("-s", "--sw-isr-table", action="store_true", + help="Generate SW ISR table") + parser.add_argument("-V", "--vector-table", action="store_true", + help="Generate vector table") + parser.add_argument("-i", "--intlist", required=True, + help="Zephyr intlist binary for intList extraction") + + args = parser.parse_args() + +source_header = """ +/* AUTO-GENERATED by gen_isr_tables.py, do not edit! */ + +#include +#include +#include + +""" + +def write_source_file(fp, vt, swt, intlist): + fp.write(source_header) + + nv = intlist["num_vectors"] + + if vt: + fp.write("uint32_t __irq_vector_table _irq_vector_table[%d] = {\n" % nv) + for i in range(nv): + fp.write("\t0x%x,\n" % vt[i]) + fp.write("};\n") + + if not swt: + return + + fp.write("struct _isr_table_entry __sw_isr_table _sw_isr_table[%d] = {\n" + % nv) + for i in range(nv): + param, func = swt[i] + fp.write("\t{(void *)0x%x, (void *)0x%x},\n" % (param, func)) + fp.write("};\n") + +def main(): + parse_args() + + intlist = read_intlist(args.intlist) + nvec = intlist["num_vectors"] + prefix = endian_prefix() + + # Set default entries in both tables + if args.sw_isr_table: + # All vectors just jump to the common sw_irq_handler. If some entries + # are used for direct interrupts, they will be replaced later. + if args.vector_table: + vt = [intlist["sw_irq_handler"] for i in range(nvec)] + else: + vt = None + # Default to spurious interrupt handler. Configured interrupts + # will replace these entries. + swt = [(0, intlist["spurious_handler"]) for i in range(nvec)] + else: + if args.vector_table: + vt = [intlist["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) + vt[irq] = func + else: + # Regular interrupt + if not swt: + error("Regular Interrupt %d declared with parameter 0x%x " + "but no SW ISR_TABLE in use" + % (irq, param)) + swt[irq] = (param, func) + + with open(args.output_source, "w") as fp: + write_source_file(fp, vt, swt, intlist) + +if __name__ == "__main__": + main() + diff --git a/arch/common/isr_tables.c b/arch/common/isr_tables.c new file mode 100644 index 00000000000..db306f7f03b --- /dev/null +++ b/arch/common/isr_tables.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#if defined(CONFIG_GEN_SW_ISR_TABLE) && defined(CONFIG_GEN_IRQ_VECTOR_TABLE) +#define ISR_WRAPPER (&_isr_wrapper) +#else +#define ISR_WRAPPER NULL +#endif + +/* 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, + */ +_GENERIC_SECTION(.irq.spurious) void *_irq_spurious_ptr = &_irq_spurious; +_GENERIC_SECTION(.irq.handler) void *_irq_handler_ptr = ISR_WRAPPER; +_GENERIC_SECTION(.irq.tablesize) uint32_t _irq_table_size = IRQ_TABLE_SIZE; + +/* These are placeholder tables. They will be replaced by the real tables + * generated by gen_isr_tables.py. + */ + +/* Some arches don't use a vector table, they have a common exception entry + * point for all interrupts. Don't generate a table in this case. + */ +#ifdef CONFIG_GEN_IRQ_VECTOR_TABLE +uint32_t __irq_vector_table _irq_vector_table[IRQ_TABLE_SIZE] = { + [0 ...(IRQ_TABLE_SIZE - 1)] = 0xabababab, +}; +#endif + +/* If there are no interrupts at all, or all interrupts are of the 'direct' + * type and bypass the _sw_isr_table, then do not generate one. + */ +#ifdef CONFIG_GEN_SW_ISR_TABLE +struct _isr_table_entry __sw_isr_table _sw_isr_table[IRQ_TABLE_SIZE] = { + [0 ...(IRQ_TABLE_SIZE - 1)] = {(void *)0xcdcdcdcd, (void *)0xcdcdcdcd}, +}; +#endif + +/* Linker needs this */ +GEN_ABS_SYM_BEGIN(isr_tables_syms) +GEN_ABSOLUTE_SYM(__ISR_LIST_SIZEOF, sizeof(struct _isr_list)); +GEN_ABS_SYM_END + diff --git a/include/linker/intlist.ld b/include/linker/intlist.ld new file mode 100644 index 00000000000..5d9756d25b1 --- /dev/null +++ b/include/linker/intlist.ld @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* This creates a special section which is not included by the final binary, + * instead it is consumed by the gen_isr_tables.py script. + * + * What we create here is a data structure: + * + * struct { + * void *spurious_irq_handler; + * void *sw_irq_handler; + * uint32_t num_isrs; + * uint32_t num_vectors; + * struct _isr_list isrs[]; <- of size num_isrs + * } + * + * Which indicates the memory address of the spurious IRQ handler and the + * number of isrs that were defined, the total number of IRQ lines in the + * system, followed by an appropriate number of instances of + * struct _isr_list. See include/sw_isr_table.h + * + * You will need to declare a bogus memory region for IDT_LIST. It doesn't + * matter where this region goes as it is stripped from the final ELF image. + * The address doesn't even have to be valid on the target. However, it + * shouldn't overlap any other regions. On most arches the following should be + * fine: + * + * MEMORY { + * .. other regions .. + * IDT_LIST : ORIGIN = 0xfffff7ff, LENGTH = 2K + * } + */ + +/* We don't set NOLOAD here, as objcopy will not let us extract the intList + * section into a file if we do so; the resulting file is 0 bytes. We do so + * with objcopy in Makefile.gen_isr_tables after we have extracted the + * information we need. + */ + +SECTION_PROLOGUE(.intList,,) +{ + KEEP(*(.irq.spurious)) + KEEP(*(.irq.handler)) + LONG((__INT_LIST_END__ - __INT_LIST_START__) / __ISR_LIST_SIZEOF) + KEEP(*(.irq.tablesize)) + __INT_LIST_START__ = .; + KEEP(*(.intList)) + __INT_LIST_END__ = .; +} GROUP_LINK_IN(IDT_LIST) + diff --git a/include/section_tags.h b/include/section_tags.h index 19d0b23eb3b..04f325e456a 100644 --- a/include/section_tags.h +++ b/include/section_tags.h @@ -15,6 +15,7 @@ #define __noinit __in_section_unique(NOINIT) #define __irq_vector_table _GENERIC_SECTION(IRQ_VECTOR_TABLE) +#define __sw_isr_table _GENERIC_SECTION(SW_ISR_TABLE) #if defined(CONFIG_ARM) #define __scs_section __in_section_unique(SCS_SECTION) diff --git a/include/sections.h b/include/sections.h index 83a33aa8b97..08a7b8b3809 100644 --- a/include/sections.h +++ b/include/sections.h @@ -40,8 +40,10 @@ #define NOINIT noinit /* Interrupts */ -#define IRQ_VECTOR_TABLE .irq_vector_table +#define IRQ_VECTOR_TABLE .gnu.linkonce.irq_vector_table +#define SW_ISR_TABLE .gnu.linkonce.sw_isr_table +/* Architecture-specific sections */ #if defined(CONFIG_ARM) #define SCS_SECTION scs #define SCP_SECTION scp diff --git a/include/sw_isr_table.h b/include/sw_isr_table.h index 48fefb1476f..174033918c1 100644 --- a/include/sw_isr_table.h +++ b/include/sw_isr_table.h @@ -21,6 +21,9 @@ extern "C" { #endif #if !defined(_ASMLANGUAGE) +#include +#include + /* * Note the order: arg first, then ISR. This allows a table entry to be * loaded arg -> r0, isr -> r3 in _isr_wrapper with one ldmia instruction, @@ -36,6 +39,40 @@ struct _isr_table_entry { */ extern struct _isr_table_entry _sw_isr_table[]; +/* + * Data structure created in a special binary .intlist section for each + * configured interrupt. gen_irq_tables.py pulls this out of the binary and + * uses it to create the IRQ vector table and the _sw_isr_table. + * + * More discussion in include/linker/intlist.ld + */ +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 */ + void *param; +}; + +/** This interrupt gets put directly in the vector table */ +#define ISR_FLAG_DIRECT (1 << 0) + +#define _MK_ISR_NAME(x, y) __isr_ ## x ## _irq_ ## y + +/* 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. + */ +#define _ISR_DECLARE(irq, flags, func, param) \ + static struct _isr_list _GENERIC_SECTION(.intList) __used \ + _MK_ISR_NAME(func, __COUNTER__) = \ + {irq, flags, &func, (void *)param} + +#define IRQ_TABLE_SIZE (CONFIG_NUM_IRQS - CONFIG_GEN_IRQ_START_VECTOR) + #endif /* _ASMLANGUAGE */ #ifdef __cplusplus