[PATCH v8 04/16] x86/virt/tdx: Add skeleton to initialize TDX on demand

From: Kai Huang
Date: Fri Dec 09 2022 - 01:53:40 EST


Before the TDX module can be used to create and run TDX guests, it must
be loaded and properly initialized. The TDX module is expected to be
loaded by the BIOS, and to be initialized by the kernel.

TDX introduces a new CPU mode: Secure Arbitration Mode (SEAM). The host
kernel communicates with the TDX module via a new SEAMCALL instruction.
The TDX module implements a set of SEAMCALL leaf functions to allow the
host kernel to initialize it.

The TDX module can be initialized only once in its lifetime. Instead
of always initializing it at boot time, this implementation chooses an
"on demand" approach to initialize TDX until there is a real need (e.g
when requested by KVM). This approach has below pros:

1) It avoids consuming the memory that must be allocated by kernel and
given to the TDX module as metadata (~1/256th of the TDX-usable memory),
and also saves the CPU cycles of initializing the TDX module (and the
metadata) when TDX is not used at all.

2) The TDX module design allows it to be updated while the system is
running. The update procedure shares quite a few steps with this "on
demand" loading mechanism. The hope is that much of "on demand"
mechanism can be shared with a future "update" mechanism. A boot-time
TDX module implementation would not be able to share much code with the
update mechanism.

3) Loading the TDX module requires VMX to be enabled. Currently, only
the kernel KVM code mucks with VMX enabling. If the TDX module were to
be initialized separately from KVM (like at boot), the boot code would
need to be taught how to muck with VMX enabling and KVM would need to be
taught how to cope with that. Making KVM itself responsible for TDX
initialization lets the rest of the kernel stay blissfully unaware of
VMX.

Add a placeholder tdx_enable() to initialize the TDX module on demand.
Use a state machine protected by mutex to make sure the initialization
will only be done once, as it can be called multiple times (i.e. KVM
module can be reloaded) and be called concurrently by other kernel
components in the future.

The TDX module will be initialized in multi-steps defined by the TDX
module and most of those steps involve a specific SEAMCALL:

1) Get the TDX module information and TDX-capable memory regions
(TDH.SYS.INFO).
2) Build the list of TDX-usable memory regions.
3) Construct a list of "TD Memory Regions" (TDMRs) to cover all
TDX-usable memory regions.
4) Pick up one TDX private KeyID as the global KeyID.
5) Configure the TDMRs and the global KeyID to the TDX module
(TDH.SYS.CONFIG).
6) Configure the global KeyID on all packages (TDH.SYS.KEY.CONFIG).
7) Initialize all TDMRs (TDH.SYS.TDMR.INIT).

Reviewed-by: Chao Gao <chao.gao@xxxxxxxxx>
Signed-off-by: Kai Huang <kai.huang@xxxxxxxxx>
---

v7 -> v8:
- Refined changelog (Dave).
- Removed "all BIOS-enabled cpus" related code (Peter/Thomas/Dave).
- Add a "TODO list" comment in init_tdx_module() to list all steps of
initializing the TDX Module to tell the story (Dave).
- Made tdx_enable() unverisally return -EINVAL, and removed nonsense
comments (Dave).
- Simplified __tdx_enable() to only handle success or failure.
- TDX_MODULE_SHUTDOWN -> TDX_MODULE_ERROR
- Removed TDX_MODULE_NONE (not loaded) as it is not necessary.
- Improved comments (Dave).
- Pointed out 'tdx_module_status' is software thing (Dave).

v6 -> v7:
- No change.

v5 -> v6:
- Added code to set status to TDX_MODULE_NONE if TDX module is not
loaded (Chao)
- Added Chao's Reviewed-by.
- Improved comments around cpus_read_lock().

- v3->v5 (no feedback on v4):
- Removed the check that SEAMRR and TDX KeyID have been detected on
all present cpus.
- Removed tdx_detect().
- Added num_online_cpus() to MADT-enabled CPUs check within the CPU
hotplug lock and return early with error message.
- Improved dmesg printing for TDX module detection and initialization.

---
arch/x86/include/asm/tdx.h | 2 +
arch/x86/virt/vmx/tdx/tdx.c | 93 +++++++++++++++++++++++++++++++++++++
2 files changed, 95 insertions(+)

diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 4dfe2e794411..4a3ee64c1ca7 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -97,8 +97,10 @@ static inline long tdx_kvm_hypercall(unsigned int nr, unsigned long p1,

#ifdef CONFIG_INTEL_TDX_HOST
bool platform_tdx_enabled(void);
+int tdx_enable(void);
#else /* !CONFIG_INTEL_TDX_HOST */
static inline bool platform_tdx_enabled(void) { return false; }
+static inline int tdx_enable(void) { return -EINVAL; }
#endif /* CONFIG_INTEL_TDX_HOST */

#endif /* !__ASSEMBLY__ */
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 292852773ced..ace9770e5e08 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -12,13 +12,25 @@
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/printk.h>
+#include <linux/mutex.h>
#include <asm/msr.h>
#include <asm/tdx.h>
#include "tdx.h"

+/* Kernel defined TDX module status during module initialization. */
+enum tdx_module_status_t {
+ TDX_MODULE_UNKNOWN,
+ TDX_MODULE_INITIALIZED,
+ TDX_MODULE_ERROR
+};
+
static u32 tdx_keyid_start __ro_after_init;
static u32 nr_tdx_keyids __ro_after_init;

+static enum tdx_module_status_t tdx_module_status;
+/* Prevent concurrent attempts on TDX detection and initialization */
+static DEFINE_MUTEX(tdx_module_lock);
+
/*
* tdx_keyid_start and nr_tdx_keyids indicate that TDX is uninitialized.
* This is used in TDX initialization error paths to take it from
@@ -88,3 +100,84 @@ bool platform_tdx_enabled(void)
{
return !!nr_tdx_keyids;
}
+
+static int init_tdx_module(void)
+{
+ /*
+ * TODO:
+ *
+ * - Get TDX module information and TDX-capable memory regions.
+ * - Build the list of TDX-usable memory regions.
+ * - Construct a list of TDMRs to cover all TDX-usable memory
+ * regions.
+ * - Pick up one TDX private KeyID as the global KeyID.
+ * - Configure the TDMRs and the global KeyID to the TDX module.
+ * - Configure the global KeyID on all packages.
+ * - Initialize all TDMRs.
+ *
+ * Return error before all steps are done.
+ */
+ return -EINVAL;
+}
+
+static int __tdx_enable(void)
+{
+ int ret;
+
+ ret = init_tdx_module();
+ if (ret) {
+ pr_err_once("initialization failed (%d)\n", ret);
+ tdx_module_status = TDX_MODULE_ERROR;
+ /*
+ * Just return one universal error code.
+ * For now the caller cannot recover anyway.
+ */
+ return -EINVAL;
+ }
+
+ pr_info_once("TDX module initialized.\n");
+ tdx_module_status = TDX_MODULE_INITIALIZED;
+
+ return 0;
+}
+
+/**
+ * tdx_enable - Enable TDX by initializing the TDX module
+ *
+ * The caller must make sure all online cpus are in VMX operation before
+ * calling this function.
+ *
+ * This function can be called in parallel by multiple callers.
+ *
+ * Return 0 if TDX is enabled successfully, otherwise error.
+ */
+int tdx_enable(void)
+{
+ int ret;
+
+ if (!platform_tdx_enabled()) {
+ pr_err_once("initialization failed: TDX is disabled.\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&tdx_module_lock);
+
+ switch (tdx_module_status) {
+ case TDX_MODULE_UNKNOWN:
+ ret = __tdx_enable();
+ break;
+ case TDX_MODULE_INITIALIZED:
+ /* Already initialized, great, tell the caller. */
+ ret = 0;
+ break;
+ default:
+ /* Failed to initialize in the previous attempts */
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&tdx_module_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(tdx_enable);
--
2.38.1