Disable global mappings when using PCID.
authorpatrick <patrick@openbsd.org>
Wed, 19 May 2021 17:46:36 +0000 (17:46 +0000)
committerpatrick <patrick@openbsd.org>
Wed, 19 May 2021 17:46:36 +0000 (17:46 +0000)
Page table mappings are frequently created and destroyed in the kernel
address space.  Traditionally, these mappings have been marked as
"global" mappings which means that a TLB flush via %cr3 load does not
invalidate them.  This is ok as these mappings are the same for all
processes.

With the advent of MELTDOWN, global mappings were disabled for CPUs
that are affected by rogue data cache load (RDCL aka MELTDOWN).  To
compensate for this we started using PCID and the kernel got its own
process context identifier.  Thus the hardware is allowed to cache
kernel mappings again.

However, a CPU that supports PCID but is _not_ affected by MELTDOWN
(i.e. ARCH_CAPABILTIES.RDCL_NO=1) will now use both: global PTE
mappings and PCID.

This is a problem if range based TLB invalidations are used to update/
flush cached TLBs after a change to the kernel page tables.  The reason
is that the invpcid instruction (function 0) that is used to remove the
cached TLBs will not remove global mappings.  In the non-PCID case invlpg
is used instead which does remove global mappings.  In the MELTDOWN case,
global mappings are not used at all.

The solution is to not use global mappings if PCID is active, as the
latter should already by enough to let the hardware cache kernel address
translations across address space switches and the global flag is not
required.

From Christian Ehrhardt
ok bluhm@ guenther@ mlarkin@

sys/arch/amd64/amd64/pmap.c

index d1bf001..49c6953 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: pmap.c,v 1.142 2021/05/10 00:52:15 guenther Exp $     */
+/*     $OpenBSD: pmap.c,v 1.143 2021/05/19 17:46:36 patrick Exp $      */
 /*     $NetBSD: pmap.c,v 1.3 2003/05/08 18:13:13 thorpej Exp $ */
 
 /*
@@ -698,6 +698,14 @@ pmap_bootstrap(paddr_t first_avail, paddr_t max_pa)
                CPUID_LEAF(0x7, 0, dummy, ebx, dummy, dummy);
                if (ebx & SEFF0EBX_INVPCID) {
                        pmap_use_pcid = 1;
+                       /*
+                        * We cannot use global mappings because
+                        * invpcid function 0 does not invalidate global
+                        * mappings. The hardware can cache kernel
+                        * mappings based on PCID_KERN, i.e. there is no
+                        * need for global mappings.
+                        */
+                       pg_g_kern = 0;
                        lcr4( rcr4() | CR4_PCIDE );
                        cr3_pcid_proc = PCID_PROC;
                        cr3_pcid_temp = PCID_TEMP;