From 94a992bccdbf656a5a7a0b585c9f140fccc02674 Mon Sep 17 00:00:00 2001
From: Juergen Gross <jgross@suse.com>
Date: Thu, 26 Apr 2018 13:33:13 +0200
Subject: [PATCH] xen/x86: use invpcid for flushing the TLB

If possible use the INVPCID instruction for flushing the TLB instead of
toggling cr4.pge for that purpose.

While at it remove the dependency on cr4.pge being required for mtrr
loading, as this will be required later anyway.

Add a command line option "invpcid" for controlling the use of
INVPCID (default to true).

Signed-off-by: Juergen Gross <jgross@suse.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>

--- a/docs/misc/xen-command-line.markdown
+++ b/docs/misc/xen-command-line.markdown
@@ -666,6 +666,15 @@ debug hypervisor only).
 ### idle\_latency\_factor
 > `= <integer>`
 
+### invpcid (x86)
+> `= <boolean>`
+
+> Default: `true`
+
+By default, Xen will use the INVPCID instruction for TLB management if
+it is available.  This option can be used to cause Xen to fall back to
+older mechanisms, which are generally slower.
+
 ### ioapic\_ack
 ### iommu
 ### iommu\_inclusive\_mapping
--- a/xen/arch/x86/cpu/mtrr/generic.c
+++ b/xen/arch/x86/cpu/mtrr/generic.c
@@ -4,6 +4,7 @@
 #include <xen/init.h>
 #include <xen/mm.h>
 #include <asm/flushtlb.h>
+#include <asm/invpcid.h>
 #include <asm/io.h>
 #include <asm/mtrr.h>
 #include <asm/msr.h>
@@ -276,7 +277,6 @@ static unsigned long set_mtrr_state(void
 }
 
 
-static unsigned long cr4 = 0;
 static DEFINE_SPINLOCK(set_atomicity_lock);
 
 /*
@@ -286,9 +286,9 @@ static DEFINE_SPINLOCK(set_atomicity_loc
  * has been called.
  */
 
-static void prepare_set(void)
+static bool_t prepare_set(void)
 {
-	unsigned long cr0;
+	unsigned long cr0, cr4;
 
 	/*  Note that this is not ideal, since the cache is only flushed/disabled
 	   for this CPU while the MTRRs are changed, but changing this requires
@@ -301,36 +301,38 @@ static void prepare_set(void)
 	write_cr0(cr0);
 	wbinvd();
 
-	/*  Save value of CR4 and clear Page Global Enable (bit 7)  */
-	if ( cpu_has_pge ) {
-		cr4 = read_cr4();
+	cr4 = read_cr4();
+	if (cr4 & X86_CR4_PGE)
 		write_cr4(cr4 & ~X86_CR4_PGE);
-	}
-
-	/* Flush all TLBs via a mov %cr3, %reg; mov %reg, %cr3 */
-	flush_tlb_local();
+	else if (use_invpcid)
+		invpcid_flush_all();
+	else
+		write_cr3(read_cr3());
 
 	/*  Save MTRR state */
 	rdmsrl(MTRRdefType_MSR, deftype);
 
 	/*  Disable MTRRs, and set the default type to uncached  */
 	mtrr_wrmsr(MTRRdefType_MSR, deftype & ~0xcff);
+
+	return cr4 & X86_CR4_PGE;
 }
 
-static void post_set(void)
+static void post_set(bool_t pge)
 {
-	/*  Flush TLBs (no need to flush caches - they are disabled)  */
-	flush_tlb_local();
-
 	/* Intel (P6) standard MTRRs */
 	mtrr_wrmsr(MTRRdefType_MSR, deftype);
 		
 	/*  Enable caches  */
 	write_cr0(read_cr0() & 0xbfffffff);
 
-	/*  Restore value of CR4  */
-	if ( cpu_has_pge )
-		write_cr4(cr4);
+	if (pge)
+		write_cr4(read_cr4() | X86_CR4_PGE);
+	else if (use_invpcid)
+		invpcid_flush_all();
+	else
+		write_cr3(read_cr3());
+
 	spin_unlock(&set_atomicity_lock);
 }
 
@@ -338,14 +340,15 @@ static void generic_set_all(void)
 {
 	unsigned long mask, count;
 	unsigned long flags;
+	bool_t pge;
 
 	local_irq_save(flags);
-	prepare_set();
+	pge = prepare_set();
 
 	/* Actually set the state */
 	mask = set_mtrr_state();
 
-	post_set();
+	post_set(pge);
 	local_irq_restore(flags);
 
 	/*  Use the atomic bitops to update the global mask  */
@@ -354,7 +357,6 @@ static void generic_set_all(void)
 			set_bit(count, &smp_changes_mask);
 		mask >>= 1;
 	}
-	
 }
 
 static void generic_set_mtrr(unsigned int reg, unsigned long base,
@@ -371,11 +373,12 @@ static void generic_set_mtrr(unsigned in
 {
 	unsigned long flags;
 	struct mtrr_var_range *vr;
+	bool_t pge;
 
 	vr = &mtrr_state.var_ranges[reg];
 
 	local_irq_save(flags);
-	prepare_set();
+	pge = prepare_set();
 
 	if (size == 0) {
 		/* The invalid bit is kept in the mask, so we simply clear the
@@ -396,7 +399,7 @@ static void generic_set_mtrr(unsigned in
 		mtrr_wrmsr(MTRRphysMask_MSR(reg), vr->mask);
 	}
 
-	post_set();
+	post_set(pge);
 	local_irq_restore(flags);
 }
 
--- a/xen/arch/x86/flushtlb.c
+++ b/xen/arch/x86/flushtlb.c
@@ -12,6 +12,7 @@
 #include <xen/smp.h>
 #include <xen/softirq.h>
 #include <asm/flushtlb.h>
+#include <asm/invpcid.h>
 #include <asm/page.h>
 
 /* Debug builds: Wrap frequently to stress-test the wrap logic. */
@@ -73,6 +74,23 @@ static void post_flush(u32 t)
     this_cpu(tlbflush_time) = t;
 }
 
+static void do_tlb_flush(void)
+{
+    u32 t = pre_flush();
+
+    if ( use_invpcid )
+        invpcid_flush_all();
+    else
+    {
+        unsigned long cr4 = read_cr4();
+
+        write_cr4(cr4 ^ X86_CR4_PGE);
+        write_cr4(cr4);
+    }
+
+    post_flush(t);
+}
+
 void switch_cr3(unsigned long cr3)
 {
     unsigned long flags;
@@ -123,23 +141,19 @@ void flush_area_local(const void *va, un
         }
         else
         {
-            u32 t = pre_flush();
-
 #ifndef USER_MAPPINGS_ARE_GLOBAL
-            if ( !(flags & FLUSH_TLB_GLOBAL) || !(read_cr4() & X86_CR4_PGE) )
+            if ( !use_invpcid &&
+                 (!(flags & FLUSH_TLB_GLOBAL) || !(read_cr4() & X86_CR4_PGE)) )
             {
+                u32 t = pre_flush();
+
                 write_cr3(read_cr3());
+                post_flush(t);
             }
             else
 #endif
-            {
-                unsigned long cr4 = read_cr4();
-                write_cr4(cr4 & ~X86_CR4_PGE);
-                barrier();
-                write_cr4(cr4);
-            }
+                do_tlb_flush();
 
-            post_flush(t);
         }
     }
 
--- a/xen/arch/x86/setup.c
+++ b/xen/arch/x86/setup.c
@@ -100,6 +100,11 @@ struct cpuinfo_x86 __read_mostly boot_cp
 unsigned long __read_mostly mmu_cr4_features =
     X86_CR4_PSE | X86_CR4_PGE | X86_CR4_PAE;
 
+/* opt_invpcid: If false, don't use INVPCID instruction even if available. */
+static bool_t __initdata opt_invpcid = 1;
+boolean_param("invpcid", opt_invpcid);
+bool_t __read_mostly use_invpcid;
+
 static int __init parse_xen_cpuid(const char *s)
 {
     const char *ss;
@@ -1352,6 +1357,9 @@ void __init __start_xen(unsigned long mb
     if ( cpu_has_fsgsbase )
         set_in_cr4(X86_CR4_FSGSBASE);
 
+    if ( opt_invpcid && cpu_has_invpcid )
+        use_invpcid = 1;
+
     init_speculation_mitigations();
 
     local_irq_enable();
--- a/xen/include/asm-x86/invpcid.h
+++ b/xen/include/asm-x86/invpcid.h
@@ -3,6 +3,8 @@
 
 #include <xen/types.h>
 
+extern bool_t use_invpcid;
+
 #define INVPCID_TYPE_INDIV_ADDR      0
 #define INVPCID_TYPE_SINGLE_CTXT     1
 #define INVPCID_TYPE_ALL_INCL_GLOBAL 2
