config EXPOLINE
        def_bool n
+       depends on $(cc-option,-mindirect-branch=thunk)
        prompt "Avoid speculative indirect branches in the kernel"
        help
          Compile the kernel with the expoline compiler options to guard
 
          If unsure, say N.
 
+config EXPOLINE_EXTERN
+       def_bool n
+       depends on EXPOLINE
+       depends on HAVE_MARCH_Z10_FEATURES
+       depends on CC_IS_GCC && GCC_VERSION >= 110200
+       depends on $(success,$(srctree)/arch/s390/tools/gcc-thunk-extern.sh $(CC))
+       prompt "Generate expolines as extern functions."
+       help
+         This option is required for some tooling like kpatch. The kernel is
+         compiled with -mindirect-branch=thunk-extern and requires a newer
+         compiler.
+
+         If unsure, say N.
+
 choice
        prompt "Expoline default"
        depends on EXPOLINE
 
 endif
 
 ifdef CONFIG_EXPOLINE
-  ifneq ($(call cc-option,$(CC_FLAGS_MARCH) -mindirect-branch=thunk),)
+  ifdef CONFIG_EXPOLINE_EXTERN
+    KBUILD_LDFLAGS_MODULE += arch/s390/lib/expoline.o
+    CC_FLAGS_EXPOLINE := -mindirect-branch=thunk-extern
+    CC_FLAGS_EXPOLINE += -mfunction-return=thunk-extern
+  else
     CC_FLAGS_EXPOLINE := -mindirect-branch=thunk
     CC_FLAGS_EXPOLINE += -mfunction-return=thunk
-    CC_FLAGS_EXPOLINE += -mindirect-branch-table
-    export CC_FLAGS_EXPOLINE
-    cflags-y += $(CC_FLAGS_EXPOLINE) -DCC_USING_EXPOLINE
-    aflags-y += -DCC_USING_EXPOLINE
   endif
+  CC_FLAGS_EXPOLINE += -mindirect-branch-table
+  export CC_FLAGS_EXPOLINE
+  cflags-y += $(CC_FLAGS_EXPOLINE) -DCC_USING_EXPOLINE
+  aflags-y += -DCC_USING_EXPOLINE
 endif
 
 ifdef CONFIG_FUNCTION_TRACER
 
  * the various thunks are merged into a single copy.
  */
        .macro __THUNK_PROLOG_NAME name
+#ifdef CONFIG_EXPOLINE_EXTERN
+       .pushsection .text,"ax",@progbits
+#else
        .pushsection .text.\name,"axG",@progbits,\name,comdat
+#endif
        .globl \name
        .hidden \name
        .type \name,@function
 555:   br      \reg
        .endm
 
+#ifdef CONFIG_EXPOLINE_EXTERN
+       .macro GEN_BR_THUNK reg,ruse=%r1
+       .endm
+       .macro GEN_BR_THUNK_EXTERN reg,ruse=%r1
+#else
        .macro GEN_BR_THUNK reg,ruse=%r1
+#endif
        __DECODE_RR __THUNK_PROLOG_BR,\reg,\ruse
        __THUNK_EX_BR \reg,\ruse
        __THUNK_EPILOG
 
 obj-y += mem.o xor.o
 lib-$(CONFIG_KPROBES) += probes.o
 lib-$(CONFIG_UPROBES) += probes.o
+obj-$(CONFIG_EXPOLINE_EXTERN) += expoline.o
 obj-$(CONFIG_S390_KPROBES_SANITY_TEST) += test_kprobes_s390.o
 test_kprobes_s390-objs += test_kprobes_asm.o test_kprobes.o
 
 
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <asm/nospec-insn.h>
+#include <linux/linkage.h>
+
+.macro GEN_ALL_BR_THUNK_EXTERN
+       .irp r1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
+       GEN_BR_THUNK_EXTERN %r\r1
+       .endr
+.endm
+
+GEN_ALL_BR_THUNK_EXTERN
 
--- /dev/null
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Borrowed from gcc: gcc/testsuite/gcc.target/s390/nobp-section-type-conflict.c
+# Checks that we don't get error: section type conflict with ‘put_page’.
+
+cat << "END" | $@ -x c - -fno-PIE -march=z10 -mindirect-branch=thunk-extern -mfunction-return=thunk-extern -mindirect-branch-table -O2 -c -o /dev/null
+int a;
+int b (void);
+void c (int);
+
+static void
+put_page (void)
+{
+  if (b ())
+    c (a);
+}
+
+__attribute__ ((__section__ (".init.text"), __cold__)) void
+d (void)
+{
+  put_page ();
+  put_page ();
+}
+END
 
                    strstarts(symname, "_savevr_") ||
                    strcmp(symname, ".TOC.") == 0)
                        return 1;
+
+       if (info->hdr->e_machine == EM_S390)
+               /* Expoline thunks are linked on all kernel modules during final link of .ko */
+               if (strstarts(symname, "__s390_indirect_jump_r"))
+                       return 1;
        /* Do not ignore this symbol */
        return 0;
 }