Skip to content

Commit e56d28d

Browse files
kaihuanghansendc
authored andcommitted
x86/virt/tdx: Configure global KeyID on all packages
After the list of TDMRs and the global KeyID are configured to the TDX module, the kernel needs to configure the key of the global KeyID on all packages using TDH.SYS.KEY.CONFIG. This SEAMCALL cannot run parallel on different cpus. Loop all online cpus and use smp_call_on_cpu() to call this SEAMCALL on the first cpu of each package. To keep things simple, this implementation takes no affirmative steps to online cpus to make sure there's at least one cpu for each package. The callers (aka. KVM) can ensure success by ensuring sufficient CPUs are online for this to succeed. Intel hardware doesn't guarantee cache coherency across different KeyIDs. The PAMTs are transitioning from being used by the kernel mapping (KeyId 0) to the TDX module's "global KeyID" mapping. This means that the kernel must flush any dirty KeyID-0 PAMT cachelines before the TDX module uses the global KeyID to access the PAMTs. Otherwise, if those dirty cachelines were written back, they would corrupt the TDX module's metadata. Aside: This corruption would be detected by the memory integrity hardware on the next read of the memory with the global KeyID. The result would likely be fatal to the system but would not impact TDX security. Following the TDX module specification, flush cache before configuring the global KeyID on all packages. Given the PAMT size can be large (~1/256th of system RAM), just use WBINVD on all CPUs to flush. If TDH.SYS.KEY.CONFIG fails, the TDX module may already have "converted" some memory for TDX module use. Convert the memory back so that it can be safely used by the kernel again. Note that this is slower than it should be because of the "partial write machine check" erratum which affects TDX-capable hardware. Also refactor and introduce a new helper: tdmr_do_pamt_func(). This takes a TDMR and runs a function on its PAMT. It looks a _bit_ odd to pass a function pointer around like this, but its use is pretty narrow and it does eliminate what would otherwise be some copying and pasting. [ dhansen: * munge changelog as usual * remove weird (*pamd_func)() syntax ] Signed-off-by: Kai Huang <kai.huang@intel.com> Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com> Reviewed-by: Isaku Yamahata <isaku.yamahata@intel.com> Reviewed-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com> Reviewed-by: Yuan Yao <yuan.yao@intel.com> Reviewed-by: Dave Hansen <dave.hansen@linux.intel.com> Link: https://lore.kernel.org/all/20231208170740.53979-14-dave.hansen%40intel.com
1 parent 554ce1c commit e56d28d

2 files changed

Lines changed: 132 additions & 2 deletions

File tree

arch/x86/virt/vmx/tdx/tdx.c

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <linux/sort.h>
2727
#include <linux/log2.h>
2828
#include <asm/page.h>
29+
#include <asm/special_insns.h>
2930
#include <asm/msr-index.h>
3031
#include <asm/msr.h>
3132
#include <asm/cpufeature.h>
@@ -606,7 +607,8 @@ static void tdmr_get_pamt(struct tdmr_info *tdmr, unsigned long *pamt_base,
606607
*pamt_size = pamt_sz;
607608
}
608609

609-
static void tdmr_free_pamt(struct tdmr_info *tdmr)
610+
static void tdmr_do_pamt_func(struct tdmr_info *tdmr,
611+
void (*pamt_func)(unsigned long base, unsigned long size))
610612
{
611613
unsigned long pamt_base, pamt_size;
612614

@@ -619,9 +621,19 @@ static void tdmr_free_pamt(struct tdmr_info *tdmr)
619621
if (WARN_ON_ONCE(!pamt_base))
620622
return;
621623

624+
pamt_func(pamt_base, pamt_size);
625+
}
626+
627+
static void free_pamt(unsigned long pamt_base, unsigned long pamt_size)
628+
{
622629
free_contig_range(pamt_base >> PAGE_SHIFT, pamt_size >> PAGE_SHIFT);
623630
}
624631

632+
static void tdmr_free_pamt(struct tdmr_info *tdmr)
633+
{
634+
tdmr_do_pamt_func(tdmr, free_pamt);
635+
}
636+
625637
static void tdmrs_free_pamt_all(struct tdmr_info_list *tdmr_list)
626638
{
627639
int i;
@@ -650,6 +662,41 @@ static int tdmrs_set_up_pamt_all(struct tdmr_info_list *tdmr_list,
650662
return ret;
651663
}
652664

665+
/*
666+
* Convert TDX private pages back to normal by using MOVDIR64B to
667+
* clear these pages. Note this function doesn't flush cache of
668+
* these TDX private pages. The caller should make sure of that.
669+
*/
670+
static void reset_tdx_pages(unsigned long base, unsigned long size)
671+
{
672+
const void *zero_page = (const void *)page_address(ZERO_PAGE(0));
673+
unsigned long phys, end;
674+
675+
end = base + size;
676+
for (phys = base; phys < end; phys += 64)
677+
movdir64b(__va(phys), zero_page);
678+
679+
/*
680+
* MOVDIR64B uses WC protocol. Use memory barrier to
681+
* make sure any later user of these pages sees the
682+
* updated data.
683+
*/
684+
mb();
685+
}
686+
687+
static void tdmr_reset_pamt(struct tdmr_info *tdmr)
688+
{
689+
tdmr_do_pamt_func(tdmr, reset_tdx_pages);
690+
}
691+
692+
static void tdmrs_reset_pamt_all(struct tdmr_info_list *tdmr_list)
693+
{
694+
int i;
695+
696+
for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++)
697+
tdmr_reset_pamt(tdmr_entry(tdmr_list, i));
698+
}
699+
653700
static unsigned long tdmrs_count_pamt_kb(struct tdmr_info_list *tdmr_list)
654701
{
655702
unsigned long pamt_size = 0;
@@ -929,6 +976,64 @@ static int config_tdx_module(struct tdmr_info_list *tdmr_list, u64 global_keyid)
929976
return ret;
930977
}
931978

979+
static int do_global_key_config(void *unused)
980+
{
981+
struct tdx_module_args args = {};
982+
983+
return seamcall_prerr(TDH_SYS_KEY_CONFIG, &args);
984+
}
985+
986+
/*
987+
* Attempt to configure the global KeyID on all physical packages.
988+
*
989+
* This requires running code on at least one CPU in each package.
990+
* TDMR initialization) will fail will fail if any package in the
991+
* system has no online CPUs.
992+
*
993+
* This code takes no affirmative steps to online CPUs. Callers (aka.
994+
* KVM) can ensure success by ensuring sufficient CPUs are online and
995+
* can run SEAMCALLs.
996+
*/
997+
static int config_global_keyid(void)
998+
{
999+
cpumask_var_t packages;
1000+
int cpu, ret = -EINVAL;
1001+
1002+
if (!zalloc_cpumask_var(&packages, GFP_KERNEL))
1003+
return -ENOMEM;
1004+
1005+
/*
1006+
* Hardware doesn't guarantee cache coherency across different
1007+
* KeyIDs. The kernel needs to flush PAMT's dirty cachelines
1008+
* (associated with KeyID 0) before the TDX module can use the
1009+
* global KeyID to access the PAMT. Given PAMTs are potentially
1010+
* large (~1/256th of system RAM), just use WBINVD.
1011+
*/
1012+
wbinvd_on_all_cpus();
1013+
1014+
for_each_online_cpu(cpu) {
1015+
/*
1016+
* The key configuration only needs to be done once per
1017+
* package and will return an error if configured more
1018+
* than once. Avoid doing it multiple times per package.
1019+
*/
1020+
if (cpumask_test_and_set_cpu(topology_physical_package_id(cpu),
1021+
packages))
1022+
continue;
1023+
1024+
/*
1025+
* TDH.SYS.KEY.CONFIG cannot run concurrently on
1026+
* different cpus. Do it one by one.
1027+
*/
1028+
ret = smp_call_on_cpu(cpu, do_global_key_config, NULL, true);
1029+
if (ret)
1030+
break;
1031+
}
1032+
1033+
free_cpumask_var(packages);
1034+
return ret;
1035+
}
1036+
9321037
static int init_tdx_module(void)
9331038
{
9341039
struct tdx_tdmr_sysinfo tdmr_sysinfo;
@@ -969,6 +1074,11 @@ static int init_tdx_module(void)
9691074
if (ret)
9701075
goto err_free_pamts;
9711076

1077+
/* Config the key of global KeyID on all packages */
1078+
ret = config_global_keyid();
1079+
if (ret)
1080+
goto err_reset_pamts;
1081+
9721082
/*
9731083
* TODO:
9741084
*
@@ -979,7 +1089,7 @@ static int init_tdx_module(void)
9791089
*/
9801090
ret = -EINVAL;
9811091
if (ret)
982-
goto err_free_pamts;
1092+
goto err_reset_pamts;
9831093

9841094
pr_info("%lu KB allocated for PAMT\n", tdmrs_count_pamt_kb(&tdx_tdmr_list));
9851095

@@ -991,6 +1101,22 @@ static int init_tdx_module(void)
9911101
put_online_mems();
9921102
return ret;
9931103

1104+
err_reset_pamts:
1105+
/*
1106+
* Part of PAMTs may already have been initialized by the
1107+
* TDX module. Flush cache before returning PAMTs back
1108+
* to the kernel.
1109+
*/
1110+
wbinvd_on_all_cpus();
1111+
/*
1112+
* According to the TDX hardware spec, if the platform
1113+
* doesn't have the "partial write machine check"
1114+
* erratum, any kernel read/write will never cause #MC
1115+
* in kernel space, thus it's OK to not convert PAMTs
1116+
* back to normal. But do the conversion anyway here
1117+
* as suggested by the TDX spec.
1118+
*/
1119+
tdmrs_reset_pamt_all(&tdx_tdmr_list);
9941120
err_free_pamts:
9951121
tdmrs_free_pamt_all(&tdx_tdmr_list);
9961122
err_free_tdmrs:
@@ -1024,6 +1150,9 @@ static int __tdx_enable(void)
10241150
* lock to prevent any new cpu from becoming online; 2) done both VMXON
10251151
* and tdx_cpu_enable() on all online cpus.
10261152
*
1153+
* This function requires there's at least one online cpu for each CPU
1154+
* package to succeed.
1155+
*
10271156
* This function can be called in parallel by multiple callers.
10281157
*
10291158
* Return 0 if TDX is enabled successfully, otherwise error.

arch/x86/virt/vmx/tdx/tdx.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
/*
1515
* TDX module SEAMCALL leaf functions
1616
*/
17+
#define TDH_SYS_KEY_CONFIG 31
1718
#define TDH_SYS_INIT 33
1819
#define TDH_SYS_RD 34
1920
#define TDH_SYS_LP_INIT 35

0 commit comments

Comments
 (0)