s390: introduce CPU alternatives
authorVasily Gorbik <gor@linux.vnet.ibm.com>
Fri, 27 Apr 2018 05:36:39 +0000 (07:36 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 29 Apr 2018 09:31:58 +0000 (11:31 +0200)
[ Upstream commit 686140a1a9c41d85a4212a1c26d671139b76404b ]

Implement CPU alternatives, which allows to optionally patch newer
instructions at runtime, based on CPU facilities availability.

A new kernel boot parameter "noaltinstr" disables patching.

Current implementation is derived from x86 alternatives. Although
ideal instructions padding (when altinstr is longer then oldinstr)
is added at compile time, and no oldinstr nops optimization has to be
done at runtime. Also couple of compile time sanity checks are done:
1. oldinstr and altinstr must be <= 254 bytes long,
2. oldinstr and altinstr must not have an odd length.

alternative(oldinstr, altinstr, facility);
alternative_2(oldinstr, altinstr1, facility1, altinstr2, facility2);

Both compile time and runtime padding consists of either 6/4/2 bytes nop
or a jump (brcl) + 2 bytes nop filler if padding is longer then 6 bytes.

.altinstructions and .altinstr_replacement sections are part of
__init_begin : __init_end region and are freed after initialization.

Signed-off-by: Vasily Gorbik <gor@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/kernel-parameters.txt
arch/s390/Kconfig
arch/s390/include/asm/alternative.h [new file with mode: 0644]
arch/s390/kernel/Makefile
arch/s390/kernel/alternative.c [new file with mode: 0644]
arch/s390/kernel/module.c
arch/s390/kernel/setup.c
arch/s390/kernel/vmlinux.lds.S

index 466c039c622b27ff5ac311526fb394131084c820..5f9e51436a99da9b7dcbc2250aab63a70cde11f1 100644 (file)
@@ -2640,6 +2640,9 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
 
        noalign         [KNL,ARM]
 
+       noaltinstr      [S390] Disables alternative instructions patching
+                       (CPU alternatives feature).
+
        noapic          [SMP,APIC] Tells the kernel to not make use of any
                        IOAPICs that may be present in the system.
 
index 9aa0d04c9dcc50f0618de6fc3a9dd1e3607d7510..f3e43262db9da8ea6675b6265b4610c0cd300db9 100644 (file)
@@ -704,6 +704,22 @@ config SECCOMP
 
          If unsure, say Y.
 
+config ALTERNATIVES
+       def_bool y
+       prompt "Patch optimized instructions for running CPU type"
+       help
+         When enabled the kernel code is compiled with additional
+         alternative instructions blocks optimized for newer CPU types.
+         These alternative instructions blocks are patched at kernel boot
+         time when running CPU supports them. This mechanism is used to
+         optimize some critical code paths (i.e. spinlocks) for newer CPUs
+         even if kernel is build to support older machine generations.
+
+         This mechanism could be disabled by appending "noaltinstr"
+         option to the kernel command line.
+
+         If unsure, say Y.
+
 endmenu
 
 menu "Power Management"
@@ -753,6 +769,7 @@ config PFAULT
 config SHARED_KERNEL
        bool "VM shared kernel support"
        depends on !JUMP_LABEL
+       depends on !ALTERNATIVES
        help
          Select this option, if you want to share the text segment of the
          Linux kernel between different VM guests. This reduces memory
diff --git a/arch/s390/include/asm/alternative.h b/arch/s390/include/asm/alternative.h
new file mode 100644 (file)
index 0000000..6c268f6
--- /dev/null
@@ -0,0 +1,163 @@
+#ifndef _ASM_S390_ALTERNATIVE_H
+#define _ASM_S390_ALTERNATIVE_H
+
+#ifndef __ASSEMBLY__
+
+#include <linux/types.h>
+#include <linux/stddef.h>
+#include <linux/stringify.h>
+
+struct alt_instr {
+       s32 instr_offset;       /* original instruction */
+       s32 repl_offset;        /* offset to replacement instruction */
+       u16 facility;           /* facility bit set for replacement */
+       u8  instrlen;           /* length of original instruction */
+       u8  replacementlen;     /* length of new instruction */
+} __packed;
+
+#ifdef CONFIG_ALTERNATIVES
+extern void apply_alternative_instructions(void);
+extern void apply_alternatives(struct alt_instr *start, struct alt_instr *end);
+#else
+static inline void apply_alternative_instructions(void) {};
+static inline void apply_alternatives(struct alt_instr *start,
+                                     struct alt_instr *end) {};
+#endif
+/*
+ * |661:       |662:     |6620      |663:
+ * +-----------+---------------------+
+ * | oldinstr  | oldinstr_padding    |
+ * |          +----------+----------+
+ * |          |          |          |
+ * |          | >6 bytes |6/4/2 nops|
+ * |          |6 bytes jg----------->
+ * +-----------+---------------------+
+ *              ^^ static padding ^^
+ *
+ * .altinstr_replacement section
+ * +---------------------+-----------+
+ * |6641:                           |6651:
+ * | alternative instr 1            |
+ * +-----------+---------+- - - - - -+
+ * |6642:               |6652:      |
+ * | alternative instr 2 | padding
+ * +---------------------+- - - - - -+
+ *                       ^ runtime ^
+ *
+ * .altinstructions section
+ * +---------------------------------+
+ * | alt_instr entries for each      |
+ * | alternative instr              |
+ * +---------------------------------+
+ */
+
+#define b_altinstr(num)        "664"#num
+#define e_altinstr(num)        "665"#num
+
+#define e_oldinstr_pad_end     "663"
+#define oldinstr_len           "662b-661b"
+#define oldinstr_total_len     e_oldinstr_pad_end"b-661b"
+#define altinstr_len(num)      e_altinstr(num)"b-"b_altinstr(num)"b"
+#define oldinstr_pad_len(num) \
+       "-(((" altinstr_len(num) ")-(" oldinstr_len ")) > 0) * " \
+       "((" altinstr_len(num) ")-(" oldinstr_len "))"
+
+#define INSTR_LEN_SANITY_CHECK(len)                                    \
+       ".if " len " > 254\n"                                           \
+       "\t.error \"cpu alternatives does not support instructions "    \
+               "blocks > 254 bytes\"\n"                                \
+       ".endif\n"                                                      \
+       ".if (" len ") %% 2\n"                                          \
+       "\t.error \"cpu alternatives instructions length is odd\"\n"    \
+       ".endif\n"
+
+#define OLDINSTR_PADDING(oldinstr, num)                                        \
+       ".if " oldinstr_pad_len(num) " > 6\n"                           \
+       "\tjg " e_oldinstr_pad_end "f\n"                                \
+       "6620:\n"                                                       \
+       "\t.fill (" oldinstr_pad_len(num) " - (6620b-662b)) / 2, 2, 0x0700\n" \
+       ".else\n"                                                       \
+       "\t.fill " oldinstr_pad_len(num) " / 6, 6, 0xc0040000\n"        \
+       "\t.fill " oldinstr_pad_len(num) " %% 6 / 4, 4, 0x47000000\n"   \
+       "\t.fill " oldinstr_pad_len(num) " %% 6 %% 4 / 2, 2, 0x0700\n"  \
+       ".endif\n"
+
+#define OLDINSTR(oldinstr, num)                                                \
+       "661:\n\t" oldinstr "\n662:\n"                                  \
+       OLDINSTR_PADDING(oldinstr, num)                                 \
+       e_oldinstr_pad_end ":\n"                                        \
+       INSTR_LEN_SANITY_CHECK(oldinstr_len)
+
+#define OLDINSTR_2(oldinstr, num1, num2)                               \
+       "661:\n\t" oldinstr "\n662:\n"                                  \
+       ".if " altinstr_len(num1) " < " altinstr_len(num2) "\n"         \
+       OLDINSTR_PADDING(oldinstr, num2)                                \
+       ".else\n"                                                       \
+       OLDINSTR_PADDING(oldinstr, num1)                                \
+       ".endif\n"                                                      \
+       e_oldinstr_pad_end ":\n"                                        \
+       INSTR_LEN_SANITY_CHECK(oldinstr_len)
+
+#define ALTINSTR_ENTRY(facility, num)                                  \
+       "\t.long 661b - .\n"                    /* old instruction */   \
+       "\t.long " b_altinstr(num)"b - .\n"     /* alt instruction */   \
+       "\t.word " __stringify(facility) "\n"   /* facility bit    */   \
+       "\t.byte " oldinstr_total_len "\n"      /* source len      */   \
+       "\t.byte " altinstr_len(num) "\n"       /* alt instruction len */
+
+#define ALTINSTR_REPLACEMENT(altinstr, num)    /* replacement */       \
+       b_altinstr(num)":\n\t" altinstr "\n" e_altinstr(num) ":\n"      \
+       INSTR_LEN_SANITY_CHECK(altinstr_len(num))
+
+#ifdef CONFIG_ALTERNATIVES
+/* alternative assembly primitive: */
+#define ALTERNATIVE(oldinstr, altinstr, facility) \
+       ".pushsection .altinstr_replacement, \"ax\"\n"                  \
+       ALTINSTR_REPLACEMENT(altinstr, 1)                               \
+       ".popsection\n"                                                 \
+       OLDINSTR(oldinstr, 1)                                           \
+       ".pushsection .altinstructions,\"a\"\n"                         \
+       ALTINSTR_ENTRY(facility, 1)                                     \
+       ".popsection\n"
+
+#define ALTERNATIVE_2(oldinstr, altinstr1, facility1, altinstr2, facility2)\
+       ".pushsection .altinstr_replacement, \"ax\"\n"                  \
+       ALTINSTR_REPLACEMENT(altinstr1, 1)                              \
+       ALTINSTR_REPLACEMENT(altinstr2, 2)                              \
+       ".popsection\n"                                                 \
+       OLDINSTR_2(oldinstr, 1, 2)                                      \
+       ".pushsection .altinstructions,\"a\"\n"                         \
+       ALTINSTR_ENTRY(facility1, 1)                                    \
+       ALTINSTR_ENTRY(facility2, 2)                                    \
+       ".popsection\n"
+#else
+/* Alternative instructions are disabled, let's put just oldinstr in */
+#define ALTERNATIVE(oldinstr, altinstr, facility) \
+       oldinstr "\n"
+
+#define ALTERNATIVE_2(oldinstr, altinstr1, facility1, altinstr2, facility2) \
+       oldinstr "\n"
+#endif
+
+/*
+ * Alternative instructions for different CPU types or capabilities.
+ *
+ * This allows to use optimized instructions even on generic binary
+ * kernels.
+ *
+ * oldinstr is padded with jump and nops at compile time if altinstr is
+ * longer. altinstr is padded with jump and nops at run-time during patching.
+ *
+ * For non barrier like inlines please define new variants
+ * without volatile and memory clobber.
+ */
+#define alternative(oldinstr, altinstr, facility)                      \
+       asm volatile(ALTERNATIVE(oldinstr, altinstr, facility) : : : "memory")
+
+#define alternative_2(oldinstr, altinstr1, facility1, altinstr2, facility2) \
+       asm volatile(ALTERNATIVE_2(oldinstr, altinstr1, facility1,          \
+                                  altinstr2, facility2) ::: "memory")
+
+#endif /* __ASSEMBLY__ */
+
+#endif /* _ASM_S390_ALTERNATIVE_H */
index 1f0fe98f6db927cea10038e5b0adbfaa57dbab93..383855ebbfb0ade847062b1104e547030db786a9 100644 (file)
@@ -75,6 +75,7 @@ obj-$(CONFIG_KPROBES)         += kprobes.o
 obj-$(CONFIG_FUNCTION_TRACER)  += mcount.o ftrace.o
 obj-$(CONFIG_CRASH_DUMP)       += crash_dump.o
 obj-$(CONFIG_UPROBES)          += uprobes.o
+obj-$(CONFIG_ALTERNATIVES)     += alternative.o
 
 obj-$(CONFIG_PERF_EVENTS)      += perf_event.o perf_cpum_cf.o perf_cpum_sf.o
 obj-$(CONFIG_PERF_EVENTS)      += perf_cpum_cf_events.o
diff --git a/arch/s390/kernel/alternative.c b/arch/s390/kernel/alternative.c
new file mode 100644 (file)
index 0000000..315986a
--- /dev/null
@@ -0,0 +1,110 @@
+#include <linux/module.h>
+#include <asm/alternative.h>
+#include <asm/facility.h>
+
+#define MAX_PATCH_LEN (255 - 1)
+
+static int __initdata_or_module alt_instr_disabled;
+
+static int __init disable_alternative_instructions(char *str)
+{
+       alt_instr_disabled = 1;
+       return 0;
+}
+
+early_param("noaltinstr", disable_alternative_instructions);
+
+struct brcl_insn {
+       u16 opc;
+       s32 disp;
+} __packed;
+
+static u16 __initdata_or_module nop16 = 0x0700;
+static u32 __initdata_or_module nop32 = 0x47000000;
+static struct brcl_insn __initdata_or_module nop48 = {
+       0xc004, 0
+};
+
+static const void *nops[] __initdata_or_module = {
+       &nop16,
+       &nop32,
+       &nop48
+};
+
+static void __init_or_module add_jump_padding(void *insns, unsigned int len)
+{
+       struct brcl_insn brcl = {
+               0xc0f4,
+               len / 2
+       };
+
+       memcpy(insns, &brcl, sizeof(brcl));
+       insns += sizeof(brcl);
+       len -= sizeof(brcl);
+
+       while (len > 0) {
+               memcpy(insns, &nop16, 2);
+               insns += 2;
+               len -= 2;
+       }
+}
+
+static void __init_or_module add_padding(void *insns, unsigned int len)
+{
+       if (len > 6)
+               add_jump_padding(insns, len);
+       else if (len >= 2)
+               memcpy(insns, nops[len / 2 - 1], len);
+}
+
+static void __init_or_module __apply_alternatives(struct alt_instr *start,
+                                                 struct alt_instr *end)
+{
+       struct alt_instr *a;
+       u8 *instr, *replacement;
+       u8 insnbuf[MAX_PATCH_LEN];
+
+       /*
+        * The scan order should be from start to end. A later scanned
+        * alternative code can overwrite previously scanned alternative code.
+        */
+       for (a = start; a < end; a++) {
+               int insnbuf_sz = 0;
+
+               instr = (u8 *)&a->instr_offset + a->instr_offset;
+               replacement = (u8 *)&a->repl_offset + a->repl_offset;
+
+               if (!test_facility(a->facility))
+                       continue;
+
+               if (unlikely(a->instrlen % 2 || a->replacementlen % 2)) {
+                       WARN_ONCE(1, "cpu alternatives instructions length is "
+                                    "odd, skipping patching\n");
+                       continue;
+               }
+
+               memcpy(insnbuf, replacement, a->replacementlen);
+               insnbuf_sz = a->replacementlen;
+
+               if (a->instrlen > a->replacementlen) {
+                       add_padding(insnbuf + a->replacementlen,
+                                   a->instrlen - a->replacementlen);
+                       insnbuf_sz += a->instrlen - a->replacementlen;
+               }
+
+               s390_kernel_write(instr, insnbuf, insnbuf_sz);
+       }
+}
+
+void __init_or_module apply_alternatives(struct alt_instr *start,
+                                        struct alt_instr *end)
+{
+       if (!alt_instr_disabled)
+               __apply_alternatives(start, end);
+}
+
+extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
+void __init apply_alternative_instructions(void)
+{
+       apply_alternatives(__alt_instructions, __alt_instructions_end);
+}
index fbc07891f9e7534abd8c150a49cb1539610dc232..186fdb7f0f3d6e62937f36705f7bf891ada234b0 100644 (file)
@@ -31,6 +31,7 @@
 #include <linux/kernel.h>
 #include <linux/moduleloader.h>
 #include <linux/bug.h>
+#include <asm/alternative.h>
 
 #if 0
 #define DEBUGP printk
@@ -428,6 +429,22 @@ int module_finalize(const Elf_Ehdr *hdr,
                    const Elf_Shdr *sechdrs,
                    struct module *me)
 {
+       const Elf_Shdr *s;
+       char *secstrings;
+
+       if (IS_ENABLED(CONFIG_ALTERNATIVES)) {
+               secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
+               for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
+                       if (!strcmp(".altinstructions",
+                                   secstrings + s->sh_name)) {
+                               /* patch .altinstructions */
+                               void *aseg = (void *)s->sh_addr;
+
+                               apply_alternatives(aseg, aseg + s->sh_size);
+                       }
+               }
+       }
+
        jump_label_apply_nops(me);
        return 0;
 }
index e974e53ab597c066a96ab05f90857ea43e569fe9..c3bdb3e80380efd5f69ebf8553ada96dbfa15f43 100644 (file)
@@ -63,6 +63,7 @@
 #include <asm/sclp.h>
 #include <asm/sysinfo.h>
 #include <asm/numa.h>
+#include <asm/alternative.h>
 #include "entry.h"
 
 /*
@@ -931,6 +932,8 @@ void __init setup_arch(char **cmdline_p)
        conmode_default();
        set_preferred_console();
 
+       apply_alternative_instructions();
+
        /* Setup zfcpdump support */
        setup_zfcpdump();
 
index 115bda280d50f89cd9512b57c79fca46b6f25f4a..b8ec50cb1b6fbc6a16d5fd608a565d67acb9cb45 100644 (file)
@@ -99,6 +99,29 @@ SECTIONS
                EXIT_DATA
        }
 
+       /*
+        * struct alt_inst entries. From the header (alternative.h):
+        * "Alternative instructions for different CPU types or capabilities"
+        * Think locking instructions on spinlocks.
+        * Note, that it is a part of __init region.
+        */
+       . = ALIGN(8);
+       .altinstructions : {
+               __alt_instructions = .;
+               *(.altinstructions)
+               __alt_instructions_end = .;
+       }
+
+       /*
+        * And here are the replacement instructions. The linker sticks
+        * them as binary blobs. The .altinstructions has enough data to
+        * get the address and the length of them to patch the kernel safely.
+        * Note, that it is a part of __init region.
+        */
+       .altinstr_replacement : {
+               *(.altinstr_replacement)
+       }
+
        /* early.c uses stsi, which requires page aligned data. */
        . = ALIGN(PAGE_SIZE);
        INIT_DATA_SECTION(0x100)