powerpc/64s: Fix stf mitigation patching w/strict RWX & hash
[linux-2.6-microblaze.git] / arch / powerpc / lib / feature-fixups.c
index 1fd31b4..fe26f2f 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/string.h>
 #include <linux/init.h>
 #include <linux/sched/mm.h>
+#include <linux/stop_machine.h>
 #include <asm/cputable.h>
 #include <asm/code-patching.h>
 #include <asm/page.h>
@@ -149,17 +150,17 @@ static void do_stf_entry_barrier_fixups(enum stf_barrier_type types)
 
                pr_devel("patching dest %lx\n", (unsigned long)dest);
 
-               patch_instruction((struct ppc_inst *)dest, ppc_inst(instrs[0]));
-
-               if (types & STF_BARRIER_FALLBACK)
+               // See comment in do_entry_flush_fixups() RE order of patching
+               if (types & STF_BARRIER_FALLBACK) {
+                       patch_instruction((struct ppc_inst *)dest, ppc_inst(instrs[0]));
+                       patch_instruction((struct ppc_inst *)(dest + 2), ppc_inst(instrs[2]));
                        patch_branch((struct ppc_inst *)(dest + 1),
-                                    (unsigned long)&stf_barrier_fallback,
-                                    BRANCH_SET_LINK);
-               else
-                       patch_instruction((struct ppc_inst *)(dest + 1),
-                                         ppc_inst(instrs[1]));
-
-               patch_instruction((struct ppc_inst *)(dest + 2), ppc_inst(instrs[2]));
+                                    (unsigned long)&stf_barrier_fallback, BRANCH_SET_LINK);
+               } else {
+                       patch_instruction((struct ppc_inst *)(dest + 1), ppc_inst(instrs[1]));
+                       patch_instruction((struct ppc_inst *)(dest + 2), ppc_inst(instrs[2]));
+                       patch_instruction((struct ppc_inst *)dest, ppc_inst(instrs[0]));
+               }
        }
 
        printk(KERN_DEBUG "stf-barrier: patched %d entry locations (%s barrier)\n", i,
@@ -227,11 +228,25 @@ static void do_stf_exit_barrier_fixups(enum stf_barrier_type types)
                                                           : "unknown");
 }
 
+static int __do_stf_barrier_fixups(void *data)
+{
+       enum stf_barrier_type *types = data;
+
+       do_stf_entry_barrier_fixups(*types);
+       do_stf_exit_barrier_fixups(*types);
+
+       return 0;
+}
 
 void do_stf_barrier_fixups(enum stf_barrier_type types)
 {
-       do_stf_entry_barrier_fixups(types);
-       do_stf_exit_barrier_fixups(types);
+       /*
+        * The call to the fallback entry flush, and the fallback/sync-ori exit
+        * flush can not be safely patched in/out while other CPUs are executing
+        * them. So call __do_stf_barrier_fixups() on one CPU while all other CPUs
+        * spin in the stop machine core with interrupts hard disabled.
+        */
+       stop_machine(__do_stf_barrier_fixups, &types, NULL);
 }
 
 void do_uaccess_flush_fixups(enum l1d_flush_type types)
@@ -284,8 +299,9 @@ void do_uaccess_flush_fixups(enum l1d_flush_type types)
                                                : "unknown");
 }
 
-void do_entry_flush_fixups(enum l1d_flush_type types)
+static int __do_entry_flush_fixups(void *data)
 {
+       enum l1d_flush_type types = *(enum l1d_flush_type *)data;
        unsigned int instrs[3], *dest;
        long *start, *end;
        int i;
@@ -309,6 +325,31 @@ void do_entry_flush_fixups(enum l1d_flush_type types)
        if (types & L1D_FLUSH_MTTRIG)
                instrs[i++] = 0x7c12dba6; /* mtspr TRIG2,r0 (SPR #882) */
 
+       /*
+        * If we're patching in or out the fallback flush we need to be careful about the
+        * order in which we patch instructions. That's because it's possible we could
+        * take a page fault after patching one instruction, so the sequence of
+        * instructions must be safe even in a half patched state.
+        *
+        * To make that work, when patching in the fallback flush we patch in this order:
+        *  - the mflr          (dest)
+        *  - the mtlr          (dest + 2)
+        *  - the branch        (dest + 1)
+        *
+        * That ensures the sequence is safe to execute at any point. In contrast if we
+        * patch the mtlr last, it's possible we could return from the branch and not
+        * restore LR, leading to a crash later.
+        *
+        * When patching out the fallback flush (either with nops or another flush type),
+        * we patch in this order:
+        *  - the branch        (dest + 1)
+        *  - the mtlr          (dest + 2)
+        *  - the mflr          (dest)
+        *
+        * Note we are protected by stop_machine() from other CPUs executing the code in a
+        * semi-patched state.
+        */
+
        start = PTRRELOC(&__start___entry_flush_fixup);
        end = PTRRELOC(&__stop___entry_flush_fixup);
        for (i = 0; start < end; start++, i++) {
@@ -316,15 +357,16 @@ void do_entry_flush_fixups(enum l1d_flush_type types)
 
                pr_devel("patching dest %lx\n", (unsigned long)dest);
 
-               patch_instruction((struct ppc_inst *)dest, ppc_inst(instrs[0]));
-
-               if (types == L1D_FLUSH_FALLBACK)
-                       patch_branch((struct ppc_inst *)(dest + 1), (unsigned long)&entry_flush_fallback,
-                                    BRANCH_SET_LINK);
-               else
+               if (types == L1D_FLUSH_FALLBACK) {
+                       patch_instruction((struct ppc_inst *)dest, ppc_inst(instrs[0]));
+                       patch_instruction((struct ppc_inst *)(dest + 2), ppc_inst(instrs[2]));
+                       patch_branch((struct ppc_inst *)(dest + 1),
+                                    (unsigned long)&entry_flush_fallback, BRANCH_SET_LINK);
+               } else {
                        patch_instruction((struct ppc_inst *)(dest + 1), ppc_inst(instrs[1]));
-
-               patch_instruction((struct ppc_inst *)(dest + 2), ppc_inst(instrs[2]));
+                       patch_instruction((struct ppc_inst *)(dest + 2), ppc_inst(instrs[2]));
+                       patch_instruction((struct ppc_inst *)dest, ppc_inst(instrs[0]));
+               }
        }
 
        start = PTRRELOC(&__start___scv_entry_flush_fixup);
@@ -334,15 +376,16 @@ void do_entry_flush_fixups(enum l1d_flush_type types)
 
                pr_devel("patching dest %lx\n", (unsigned long)dest);
 
-               patch_instruction((struct ppc_inst *)dest, ppc_inst(instrs[0]));
-
-               if (types == L1D_FLUSH_FALLBACK)
-                       patch_branch((struct ppc_inst *)(dest + 1), (unsigned long)&scv_entry_flush_fallback,
-                                    BRANCH_SET_LINK);
-               else
+               if (types == L1D_FLUSH_FALLBACK) {
+                       patch_instruction((struct ppc_inst *)dest, ppc_inst(instrs[0]));
+                       patch_instruction((struct ppc_inst *)(dest + 2), ppc_inst(instrs[2]));
+                       patch_branch((struct ppc_inst *)(dest + 1),
+                                    (unsigned long)&scv_entry_flush_fallback, BRANCH_SET_LINK);
+               } else {
                        patch_instruction((struct ppc_inst *)(dest + 1), ppc_inst(instrs[1]));
-
-               patch_instruction((struct ppc_inst *)(dest + 2), ppc_inst(instrs[2]));
+                       patch_instruction((struct ppc_inst *)(dest + 2), ppc_inst(instrs[2]));
+                       patch_instruction((struct ppc_inst *)dest, ppc_inst(instrs[0]));
+               }
        }
 
 
@@ -354,6 +397,19 @@ void do_entry_flush_fixups(enum l1d_flush_type types)
                                                        : "ori type" :
                (types &  L1D_FLUSH_MTTRIG)     ? "mttrig type"
                                                : "unknown");
+
+       return 0;
+}
+
+void do_entry_flush_fixups(enum l1d_flush_type types)
+{
+       /*
+        * The call to the fallback flush can not be safely patched in/out while
+        * other CPUs are executing it. So call __do_entry_flush_fixups() on one
+        * CPU while all other CPUs spin in the stop machine core with interrupts
+        * hard disabled.
+        */
+       stop_machine(__do_entry_flush_fixups, &types, NULL);
 }
 
 void do_rfi_flush_fixups(enum l1d_flush_type types)