Skip to contentSkip to conversation

KVM TDX basic feature support

PATCH In review [email protected] 21 participants 695 messages Feb 26, 2024 – Jul 18, 2024 Page 1 of 6

Subsystems (8)
Documentation x86 KVM Kselftest +4 more subsystems
lore
Share & tools
lore
#0 lore raw

v19 cover KVM TDX basic feature support

From: Isaku Yamahata <[email protected]>

KVM TDX basic feature support

Hello. This is v19 the patch series of KVM TDX support. This is based on v6.8-rc5.

Major changes and uAPI
----------------------
The major change is uAPI change to use KVM_MEMORY_MAPPING as KVM common implementation and dropped CONFIG_KVM_MMU_PRIVATE. uAPI change requires patches to qemu or tdx kselftest as follows.

For unified KVM uAPI, I'll post independent patches and TDX support would be on top of this patch series.

Trees
-----
The tree can be found at https://github.com/intel/tdx/tree/kvm-upstream
The corresponding qemu branch is found at
https://github.com/yamahata/qemu/tree/tdx/qemu-upm
How to run/test: It's describe at https://github.com/intel/tdx/wiki/TDX-KVM

Existing VM type and SNP
------------------------
The following matrix shows helper functions returns and how the code distinguish the existing VM types (default, sw protected, and SNP) from TDX so that this patch series doesn't break the existing code.

                        default or SEV-SNP      TDX: S = (47 or 51) - 12
gfn_shared_mask         0                       S bit
kvm_is_private_gpa()    always false            true if GFN has S bit set
kvm_gfn_to_shared()     nop                     set S bit
kvm_gfn_to_private()    nop                     clear S bit

fault.is_private means that host page should be gotten from guest_memfd is_private_gpa() means that KVM MMU should invoke private MMU hooks.

Future work
-----------
This patch series supports only basic functionalities, TDX guest creation, execution and destruction. The following features are planned as follow up.
- vPMU
- Off-TD debug. qemu gdb stub support
- live migration
- etc

Required changes to qemu and tdx kselftest
------------------------------------------
Required changes to QEMU:
Please don't forget to sync linux-headers.

target/i386/kvm/tdx.c +25 -12
index 0041be589c21..60c70b6bed79 100644
@@ -714,11 +714,7 @@ static void tdx_finalize_vm(Notifier *notifier, void *unused)
     tdx_post_init_vcpus();
 
     for_each_tdx_fw_entry(tdvf, entry) {
-        struct kvm_tdx_init_mem_region mem_region = {
-            .source_addr = (__u64)entry->mem_ptr,
-            .gpa = entry->address,
-            .nr_pages = entry->size / 4096,
-        };
+        struct kvm_memory_mapping mapping;
 
         r = kvm_set_memory_attributes_private(entry->address, entry->size);
         if (r < 0) {
@@ -726,14 +722,31 @@ static void tdx_finalize_vm(Notifier *notifier, void *unused)
              exit(1);
         }
 
-        __u32 flags = entry->attributes & TDVF_SECTION_ATTRIBUTES_MR_EXTEND ?
-                      KVM_TDX_MEASURE_MEMORY_REGION : 0;
-
-        trace_kvm_tdx_init_mem_region(entry->type, entry->attributes, mem_region.source_addr, mem_region.gpa, mem_region.nr_pages);
-        r = tdx_vm_ioctl(KVM_TDX_INIT_MEM_REGION, flags, &mem_region);
+        mapping = (struct kvm_memory_mapping) {
+            .base_gfn = entry->address / 4096,
+            .nr_pages = entry->size / 4096,
+            .source = (__u64)entry->mem_ptr,
+        };
+        do {
+            r = kvm_vcpu_ioctl(first_cpu, KVM_MEMORY_MAPPING, &mapping);
+        } while (r == -EAGAIN);
         if (r < 0) {
-             error_report("KVM_TDX_INIT_MEM_REGION failed %s", strerror(-r));
-             exit(1);
+            error_report("KVM_MEMORY_MAPPING failed %s", strerror(-r));
+            exit(1);
+        }
+
+        if (entry->attributes & TDVF_SECTION_ATTRIBUTES_MR_EXTEND) {
+            mapping = (struct kvm_memory_mapping) {
+                .base_gfn = entry->address / 4096,
+                .nr_pages = entry->size / 4096,
+            };
+            do {
+                r = tdx_vm_ioctl(KVM_TDX_EXTEND_MEMORY, 0, &mapping);
+            } while (r == -EAGAIN);
+            if (r < 0) {
+                error_report("KVM_TDX_EXTEND_MEMORY failed %s", strerror(-r));
+                exit(1);
+            }
         }
 
         if (entry->type == TDVF_SECTION_TYPE_TD_HOB ||

--

Required changes to tdx kselftest.

tools/testing/selftests/kvm/lib/x86_64/tdx/tdx_util.c +19 -9
index edc1b227a014..a2f4921c416f 100644
@@ -23,7 +23,7 @@ static char *tdx_cmd_str[] = {
 	"KVM_TDX_CAPABILITIES",
 	"KVM_TDX_INIT_VM",
 	"KVM_TDX_INIT_VCPU",
-	"KVM_TDX_INIT_MEM_REGION",
+	"KVM_TDX_EXTEND_MEMORY",
 	"KVM_TDX_FINALIZE_VM",
 	"KVM_TDX_RELEASE_VM"
 };
@@ -179,19 +179,29 @@ static void tdx_td_vcpu_init(struct kvm_vcpu *vcpu)
 static void tdx_init_mem_region(struct kvm_vm *vm, void *source_pages,
 				uint64_t gpa, uint64_t size)
 {
-	struct kvm_tdx_init_mem_region mem_region = {
-		.source_addr = (uint64_t)source_pages,
-		.gpa = gpa,
+	struct kvm_vcpu * vcpu = list_first_entry(&vm->vcpus,
+						  struct kvm_vcpu, list);
+	struct kvm_memory_mapping mapping = {
+		.base_gfn = gpa / PAGE_SIZE,
 		.nr_pages = size / PAGE_SIZE,
+		.source = (uint64_t)source_pages,
 	};
-	uint32_t metadata = KVM_TDX_MEASURE_MEMORY_REGION;
+	int r;
 
-	TEST_ASSERT((mem_region.nr_pages > 0) &&
-			    ((mem_region.nr_pages * PAGE_SIZE) == size),
+	TEST_ASSERT((mapping.nr_pages > 0) &&
+		    ((mapping.nr_pages * PAGE_SIZE) == size),
 		    "Cannot add partial pages to the guest memory.\n");
 	TEST_ASSERT(((uint64_t)source_pages & (PAGE_SIZE - 1)) == 0,
 		    "Source memory buffer is not page aligned\n");
-	tdx_ioctl(vm->fd, KVM_TDX_INIT_MEM_REGION, metadata, &mem_region);
+	r = ioctl(vcpu->fd, KVM_MEMORY_MAPPING, &mapping);
+	TEST_ASSERT(r == 0, "KVM_MEMORY_MAPPING failed: %d %d",
+		    r, errno);
+
+	mapping = (struct kvm_memory_mapping) {
+		.base_gfn = gpa / PAGE_SIZE,
+		.nr_pages = size / PAGE_SIZE,
+	};
+	tdx_ioctl(vm->fd, KVM_TDX_EXTEND_MEMORY, 0, &mapping);
 }
 
 static void tdx_td_finalizemr(struct kvm_vm *vm)
@@ -231,7 +241,7 @@ static void tdx_enable_capabilities(struct kvm_vm *vm)
 		      KVM_X2APIC_API_USE_32BIT_IDS |
 			      KVM_X2APIC_API_DISABLE_BROADCAST_QUIRK);
 	vm_enable_cap(vm, KVM_CAP_SPLIT_IRQCHIP, 24);
-	vm_enable_cap(vm, KVM_CAP_MAX_VCPUS, 1024);
+	vm_enable_cap(vm, KVM_CAP_MAX_VCPUS, 512);
 }
 
 static void tdx_configure_memory_encryption(struct kvm_vm *vm)

--
Isaku Yamahata

Changes from v18:
- rebased to v6.8-rc5:
- uAPI brekage: KVM_TDX_INIT_MEM_REGION => KVM_MEMORY_MAPPING + KVM_TDX_EXTEND_MEMORY
- Drop CONFIG_KVM_MMU_PRIVATE
- Require NO_RBP_MOD extension of TDX module
- Use TDH.VP.RD(TD_VCPU_STATE_DETAILS) to drop buggy_hlt_workaround

Changes from v17:
- Changed tdx_seamcall() to use struct tdx_module_args
- use TDH.SYS.RD() instead of TDH.SYS.INFO()
- Drop workaround of pending interrupt
- move the initializatin of loaded_vmcss_on_cpu to vmx_init()
- More error handling on tdx 1.5 specific error code
- drop changes of tools/arch/x86/include/uapi/asm/kvm.h
- fixes typo, indent and tabs

Changes from v16:
- rebased v6.7-rc
- Switched to TDX module 1.5. Unsupport TDX module 1.0

Changes from v15:
- Added KVM_TDX_RELEASE_VM to reduce the destruction time
- Catch up the TDX module interface to use struct tdx_module_args instead of struct tdx_module_output
- Add tdh_mem_sept_rd() for SEPT_VE_DISABLE=1 and handle Secure-EPT violation with SEPT_VE_DISABLE case.
- Simplified tdx_reclaim_page()
- Reorganize the locking of tdx_release_hkid(), and use smp_call_mask() instead of smp_call_on_cpu() to hold spinlock to race with invalidation on releasing guest memfd
- Removed AMX check as the KVM upstream supports AMX.
- Added CET flag to guest supported xss
- add check if nr_pages isn't large with
(nr_page << PAGE_SHIFT) >> PAGE_SHIFT
- use __seamcall_saved_ret()
- As struct tdx_module_args doesn't match with vcpu.arch.regs, copy regs before/after calling __seamcall_saved_ret().

Changes from v14:
https://lore.kernel.org/all/[email protected]/
- rebased to v6.5-rc2, v11 KVM guest_memfd(), v11 TDX host kernel support
- ABI change to add reserved member for future compatibility, dropped unused member.
- handle EXIT_REASON_OTHER_SMI
- handle FEAT_CTL MSR access

Changes from v13:
- rbased to v6.4-rc3
- Make use of KVM gmem.
- Added check_cpuid callback for KVM_SET_CPUID2 as RFC patch.
- ABI change of KVM_TDX_VM_INIT as VM scoped KVM ioctl.
- Make TDX initialization non-depend on kvm hardware_enable. Use vmx_hardware_enable directly.
- Drop a patch to prohibit dirty logging as new KVM gmem code base
- Drop parameter only checking for some TDG.VP.VMCALL. Just default part

Changes from v12:
- ABI change of KVM_TDX_VM_INIT
- Rename kvm_gfn_{private, shared} to kvm_gfn_to_{private, shared}
- Move APIC BASE MSI initialization to KVM_TDX_VCPU_INIT
- Fix MTRR patch
- Make MapGpa hypercall always pass it to user space VMM
- Split hooks to TDP MMU into two part. populating and zapping.

Changes from v11:
- ABI change of KVM_TDX_VM_INIT
- Split the hook of TDP MMU to not modify handle_changed_spte()
- Enhanced commit message on mtrr patch
- Made KVM_CAP_MAX_VCPUS to x86 specific

Changes from v10:
- rebased to v6.2-rc3
- support mtrr with its own patches
- Integrated fd-based private page v10
- Integrated TDX host kernel support v8
- Integrated kvm_init rework v2
- removed struct tdx_td_page and its initialization logic
- cleaned up mmio spte and require enable_mmio_caching=true for TDX
- removed dubious WARN_ON_ONCE()
- split a patch adding methods as nop into several patches

Changes from v9:
- rebased to v6.1-rc2
- Integrated fd-based private page v9 as prerequisite.
- Integrated TDX host kernel support v6
- TDP MMU: Make handle_change_spte() return value.
- TDX: removed seamcall_lock and return -EAGAIN so that TDP MMU can retry

Changes from v8:
- rebased to v6.0-rc7
- Integrated with kvm hardware initialization. Check all packages has at least one online CPU when creating guest TD and refuse cpu offline during guest TDs are running.
- Integrated fd-based private page v8 as prerequisite.
- TDP MMU: Introduced more callbacks instead of single callback.

Changes from v7:
- Use xarray to track whether GFN is private or shared. Drop SPTE_SHARED_MASK. The complex state machine with SPTE_SHARED_MASK was ditched.
- Large page support is implemented. But will be posted as independent RFC patch.
- fd-based private page v7 is integrated. This is mostly same to Chao's patches. It's in github.

Changes from v6:
- rebased to v5.19

Changes from v5:
- export __seamcall and use it
- move mutex lock from callee function of smp_call_on_cpu to the caller.
- rename mmu_prezap => flush_shadow_all_private() and tdx_mmu_release_hkid
- updated comment
- drop the use of tdh_mng_key.reclaimid(): as the function is for backward compatibility to only return success
- struct kvm_tdx_cmd: metadata => flags, added __u64 error.
- make this ioctl systemwide ioctl
- ABI change to struct kvm_init_vm
- guest_tsc_khz: use kvm->arch.default_tsc_khz
- rename BUILD_BUG_ON_MEMCPY to MEMCPY_SAME_SIZE
- drop exporting kvm_set_tsc_khz().
- fix kvm_tdp_page_fault() for mtrr emulation
- rename it to kvm_gfn_shared_mask(), dropped kvm_gpa_shared_mask()
- drop kvm_is_private_gfn(), kept kvm_is_private_gpa()
keep kvm_{gfn, gpa}_private(), kvm_gpa_private()
- update commit message
- rename shadow_init_value => shadow_nonprsent_value
- added ept_violation_ve_test mode
- shadow_nonpresent_value => SHADOW_NONPRESENT_VALUE in tdp_mmu.c
- legacy MMU case
=> - mmu_topup_shadow_page_cache(), kvm_mmu_create()
- FNAME(sync_page)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp)
- #VE warning:
- rename: REMOVED_SPTE => __REMOVED_SPTE, SHADOW_REMOVED_SPTE => REMOVED_SPTE
- merge into Like we discussed, this patch should be merged with patch "KVM: x86/mmu: Allow non-zero init value for shadow PTE".
- fix pointed by Sagi. check !is_private check => (kvm_gfn_shared_mask && !is_private)
- introduce kvm_gfn_for_root(kvm, root, gfn)
- add only_shared argument to kvm_tdp_mmu_handle_gfn()
- use kvm_arch_dirty_log_supported()
- rename SPTE_PRIVATE_PROHIBIT to SPTE_SHARED_MASK.
- rename: is_private_prohibit_spte() => spte_shared_mask()
- fix: shadow_nonpresent_value => SHADOW_NONPRESENT_VALUE in comment
- dropped this patch as the change was merged into kvm/queue
- update vt_apicv_post_state_restore()
- use is_64_bit_hypercall()
- comment: expand MSMI -> Machine Check System Management Interrupt
- fixed TDX_SEPT_PFERR
- tdvmcall_p[1234]_{write, read}() => tdvmcall_a[0123]_{read,write}()
- rename tdmvcall_exit_readon() => tdvmcall_leaf()
- remove optional zero check of argument.
- do a check for static_call(kvm_x86_has_emulated_msr)(kvm, MSR_IA32_SMBASE) in kvm_vcpu_ioctl_smi and __apic_accept_irq.
- WARN_ON_ONCE in tdx_smi_allowed and tdx_enable_smi_window.
- introduce vcpu_deliver_init to x86_ops
- sprinkeled KVM_BUG_ON()

Changes from v4:
- rebased to TDX host kernel patch series.
- include all the patches to make this patch series working.
- add [MARKER] patches to mark the patch layer clear.

---
* What's TDX?
TDX stands for Trust Domain Extensions, which extends Intel Virtual Machines Extensions (VMX) to introduce a kind of virtual machine guest called a Trust Domain (TD) for confidential computing.

A TD runs in a CPU mode that is designed to protect the confidentiality of its memory contents and its CPU state from any other software, including the hosting Virtual Machine Monitor (VMM), unless explicitly shared by the TD itself.

We have more detailed explanations below (***). We have the high-level design of TDX KVM below (****).

In this patch series, we use "TD" or "guest TD" to differentiate it from the current "VM" (Virtual Machine), which is supported by KVM today.

* The organization of this patch series
This patch series is on top of the patches series "TDX host kernel support":
https://lore.kernel.org/lkml/[email protected]/

This patch series is available at
https://github.com/intel/tdx/tree/kvm-upstream

The related repositories (TDX qemu, TDX OVMF(tdvf) etc) are described at
https://github.com/intel/tdx/wiki/TDX-KVM

The relations of the layers are depicted as follows.
The arrows below show the order of patch reviews we would like to have.

The below layers are chosen so that the device model, for example, qemu can exercise each layering step by step. Check if TDX is supported, create TD VM, create TD vcpu, allow vcpu running, populate TD guest private memory, and handle vcpu exits/hypercalls/interrupts to run TD fully.

TDX vcpu
interrupt/exits/hypercall<------------\

        ^                               |
        |                               |
  TD finalization                       |
        ^                               |
        |                               |
  TDX EPT violation<------------\       |
        ^                       |       |
        |                       |       |
  TD vcpu enter/exit            |       |
        ^                       |       |
        |                       |       |
  TD vcpu creation/destruction  |       \-------KVM TDP MMU MapGPA
        ^                       |                       ^
        |                       |                       |
  TD VM creation/destruction    \---------------KVM TDP MMU hooks

^ ^
| |
TDX architectural definitions KVM TDP refactoring for TDX
^ ^
| |
TDX, VMX <--------TDX host kernel KVM MMU GPA stolen bits
coexistence support

The followings are explanations of each layer. Each layer has a dummy commit that starts with [MARKER] in subject. It is intended to help to identify where each layer starts.

TDX host kernel support:
https://lore.kernel.org/lkml/[email protected]/
The guts of system-wide initialization of TDX module. There is an independent patch series for host x86. TDX KVM patches call functions this patch series provides to initialize the TDX module.

TDX, VMX coexistence:
Infrastructure to allow TDX to coexist with VMX and trigger the initialization of the TDX module.
This layer starts with
"KVM: VMX: Move out vmx_x86_ops to 'main.c' to wrap VMX and TDX"
TDX architectural definitions:
Add TDX architectural definitions and helper functions
This layer starts with
"[MARKER] The start of TDX KVM patch series: TDX architectural definitions".
TD VM creation/destruction:
Guest TD creation/destroy allocation and releasing of TDX specific vm and vcpu structure. Create an initial guest memory image with TDX measurement.
This layer starts with
"[MARKER] The start of TDX KVM patch series: TD VM creation/destruction".
TD vcpu creation/destruction:
guest TD creation/destroy Allocation and releasing of TDX specific vm and vcpu structure. Create an initial guest memory image with TDX measurement.
This layer starts with
"[MARKER] The start of TDX KVM patch series: TD vcpu creation/destruction"
TDX EPT violation:
Create an initial guest memory image with TDX measurement. Handle secure EPT violations to populate guest pages with TDX SEAMCALLs. This layer starts with
"[MARKER] The start of TDX KVM patch series: TDX EPT violation"
TD vcpu enter/exit:
Allow TDX vcpu to enter into TD and exit from TD. Save CPU state before entering into TD. Restore CPU state after exiting from TD.
This layer starts with
"[MARKER] The start of TDX KVM patch series: TD vcpu enter/exit"
TD vcpu interrupts/exit/hypercall:
Handle various exits/hypercalls and allow interrupts to be injected so that TD vcpu can continue running.
This layer starts with
"[MARKER] The start of TDX KVM patch series: TD vcpu exits/interrupts/hypercalls"

KVM MMU GPA shared bit:
Introduce framework to handle shared bit repurposed bit of GPA TDX repurposed a bit of GPA to indicate shared or private. If it's shared, it's the same as the conventional VMX EPT case. VMM can access shared guest pages. If it's private, it's handled by Secure-EPT and the guest page is encrypted.
This layer starts with
"[MARKER] The start of TDX KVM patch series: KVM MMU GPA stolen bits"
KVM TDP refactoring for TDX:
TDX Secure EPT requires different constants. e.g. initial value EPT entry value etc. Various refactoring for those differences. This layer starts with
"[MARKER] The start of TDX KVM patch series: KVM TDP refactoring for TDX"
KVM TDP MMU hooks:
Introduce framework to TDP MMU to add hooks in addition to direct EPT access TDX added Secure EPT which is an enhancement to VMX EPT. Unlike conventional VMX EPT, CPU can't directly read/write Secure EPT. Instead, use TDX SEAMCALLs to operate on Secure EPT.
This layer starts with
"[MARKER] The start of TDX KVM patch series: KVM TDP MMU hooks"
KVM TDP MMU MapGPA:
Introduce framework to handle switching guest pages from private/shared to shared/private. For a given GPA, a guest page can be assigned to a private GPA or a shared GPA exclusively. With TDX MapGPA hypercall, guest TD converts GPA assignments from private (or shared) to shared (or private).
This layer starts with
"[MARKER] The start of TDX KVM patch series: KVM TDP MMU MapGPA "

KVM guest private memory: (not shown in the above diagram)
[PATCH v4 00/12] KVM: mm: fd-based approach for supporting KVM guest private
memory: https://lkml.org/lkml/2022/1/18/395
Guest private memory requires different memory management in KVM. The patch proposes a way for it. Integration with TDX KVM.

(***)
* TDX module
A CPU-attested software module called the "TDX module" is designed to implement the TDX architecture, and it is loaded by the UEFI firmware today. It can be loaded by the kernel or driver at runtime, but in this patch series we assume that the TDX module is already loaded and initialized.

The TDX module provides two main new logical modes of operation built upon the new SEAM (Secure Arbitration Mode) root and non-root CPU modes added to the VMX architecture. TDX root mode is mostly identical to the VMX root operation mode, and the TDX functions (described later) are triggered by the new SEAMCALL instruction with the desired interface function selected by an input operand (leaf number, in RAX). TDX non-root mode is used for TD guest operation. TDX non-root operation (i.e. "guest TD" mode) is similar to the VMX non-root operation (i.e. guest VM), with changes and restrictions to better assure that no other software or hardware has direct visibility of the TD memory and state.

TDX transitions between TDX root operation and TDX non-root operation include TD Entries, from TDX root to TDX non-root mode, and TD Exits from TDX non-root to TDX root mode. A TD Exit might be asynchronous, triggered by some external event (e.g., external interrupt or SMI) or an exception, or it might be synchronous, triggered by a TDCALL (TDG.VP.VMCALL) function.

TD VCPUs can be entered using SEAMCALL(TDH.VP.ENTER) by KVM. TDH.VP.ENTER is one of the TDX interface functions as mentioned above, and "TDH" stands for Trust Domain Host. Those host-side TDX interface functions are categorized into various areas just for better organization, such as SYS (TDX module management), MNG (TD management), VP (VCPU), PHYSMEM (physical memory), MEM (private memory), etc. For example, SEAMCALL(TDH.SYS.INFO) returns the TDX module information.

TDCS (Trust Domain Control Structure) is the main control structure of a guest TD, and encrypted (using the guest TD's ephemeral private key). At a high level, TDCS holds information for controlling TD operation as a whole, execution, EPTP, MSR bitmaps, etc that KVM needs to set it up. Note that MSR bitmaps are held as part of TDCS (unlike VMX) because they are meant to have the same value for all VCPUs of the same TD.

Trust Domain Virtual Processor State (TDVPS) is the root control structure of a TD VCPU. It helps the TDX module control the operation of the VCPU, and holds the VCPU state while the VCPU is not running. TDVPS is opaque to software and DMA access, accessible only by using the TDX module interface functions (such as TDH.VP.RD, TDH.VP.WR). TDVPS includes TD VMCS, and TD VMCS auxiliary structures, such as virtual APIC page, virtualization exception information, etc.

Several VMX control structures (such as Shared EPT and Posted interrupt descriptor) are directly managed and accessed by the host VMM. These control structures are pointed to by fields in the TD VMCS.

The above means that 1) KVM needs to allocate different data structures for TDs,
2) KVM can reuse the existing code for TDs for some operations, 3) it needs to define TD-specific handling for others. 3) Redirect operations to . 3) Redirect operations to the TDX specific callbacks, like "if (is_td_vcpu(vcpu)) tdx_callback() else vmx_callback();".

*TD Private Memory
TD private memory is designed to hold TD private content, encrypted by the CPU using the TD ephemeral key. An encryption engine holds a table of encryption keys, and an encryption key is selected for each memory transaction based on a Host Key Identifier (HKID). By design, the host VMM does not have access to the encryption keys.

In the first generation of MKTME, HKID is "stolen" from the physical address by allocating a configurable number of bits from the top of the physical address. The HKID space is partitioned into shared HKIDs for legacy MKTME accesses and private HKIDs for SEAM-mode-only accesses. We use 0 for the shared HKID on the host so that MKTME can be opaque or bypassed on the host.

During TDX non-root operation (i.e. guest TD), memory accesses can be qualified as either shared or private, based on the value of a new SHARED bit in the Guest Physical Address (GPA). The CPU translates shared GPAs using the usual VMX EPT (Extended Page Table) or "Shared EPT" (in this document), which resides in host VMM memory. The Shared EPT is directly managed by the host VMM - the same as with the current VMX. Since guest TDs usually require I/O, and the data exchange needs to be done via shared memory, thus KVM needs to use the current EPT functionality even for TDs.

* Secure EPT and Minoring using the TDP code
The CPU translates private GPAs using a separate Secure EPT. The Secure EPT pages are encrypted and integrity-protected with the TD's ephemeral private key. Secure EPT can be managed _indirectly_ by the host VMM, using the TDX interface functions, and thus conceptually Secure EPT is a subset of EPT (why "subset"). Since execution of such interface functions takes much longer time than accessing memory directly, in KVM we use the existing TDP code to minor the Secure EPT for the TD.

This way, we can effectively walk Secure EPT without using the TDX interface functions.

* VM life cycle and TDX specific operations
The userspace VMM, such as QEMU, needs to build and treat TDs differently. For example, a TD needs to boot in private memory, and the host software cannot copy the initial image to private memory.

* TSC Virtualization
The TDX module helps TDs maintain reliable TSC (Time Stamp Counter) values (e.g. consistent among the TD VCPUs) and the virtual TSC frequency is determined by TD configuration, i.e. when the TD is created, not per VCPU. The current KVM owns TSC virtualization for VMs, but the TDX module does for TDs.

* MCE support for TDs
The TDX module doesn't allow VMM to inject MCE. Instead PV way is needed for TD to communicate with VMM. For now, KVM silently ignores MCE request by VMM. MSRs related to MCE (e.g, MCE bank registers) can be naturally emulated by paravirtualizing MSR access.

[1] For details, the specifications, [2], [3], [4], [5], [6], [7], are available.

* Restrictions or future work
Some features are not included to reduce patch size. Those features are addressed as future independent patch series.
- large page (2M, 1G)
- qemu gdb stub
- guest PMU
- and more

* Prerequisites
It's required to load the TDX module and initialize it. It's out of the scope of this patch series. Another independent patch for the common x86 code is planned. It defines CONFIG_INTEL_TDX_HOST and this patch series uses CONFIG_INTEL_TDX_HOST. It's assumed that With CONFIG_INTEL_TDX_HOST=y, the TDX module is initialized and ready for KVM to use the TDX module APIs for TDX guest life cycle like tdh.mng.init are ready to use.

Concretely Global initialization, LP (Logical Processor) initialization, global configuration, the key configuration, and TDMR and PAMT initialization are done. The state of the TDX module is SYS_READY. Please refer to the TDX module specification, the chapter Intel TDX Module Lifecycle State Machine

** Detecting the TDX module readiness.
TDX host patch series implements the detection of the TDX module availability and its initialization so that KVM can use it. Also it manages Host KeyID (HKID) assigned to guest TD.
The assumed APIs the TDX host patch series provides are
- const struct tdsysinfo_struct *tdx_get_sysinfo(void);
Return the system wide information about the TDX module. NULL if the TDX isn't initialized.
- int tdx_enable(void);
Initialization of TDX module so that the TDX module is ready for KVM to use.
- extern u32 tdx_global_keyid __read_mostly;
global host key id that is used for the TDX module itself.
- u32 tdx_get_num_keyid(void);
return the number of available TDX private host key id.
- int tdx_keyid_alloc(void);
Allocate HKID for guest TD.
- void tdx_keyid_free(int keyid);
Free HKID for guest TD.

(****)
* TDX KVM high-level design
- Host key ID management
Host Key ID (HKID) needs to be assigned to each TDX guest for memory encryption. It is assumed The TDX host patch series implements necessary functions, u32 tdx_get_global_keyid(void), int tdx_keyid_alloc(void) and,
void tdx_keyid_free(int keyid).

- Data structures and VM type
Because TDX is different from VMX, define its own VM/VCPU structures, struct kvm_tdx and struct vcpu_tdx instead of struct kvm_vmx and struct vcpu_vmx. To identify the VM, introduce VM-type to specify which VM type, VMX (default) or TDX, is used.

- VM life cycle and TDX specific operations
Re-purpose the existing KVM_MEMORY_ENCRYPT_OP to add TDX specific operations. New commands are used to get the TDX system parameters, set TDX specific VM/VCPU parameters, set initial guest memory and measurement.

The creation of TDX VM requires five additional operations in addition to the conventional VM creation.
- Get KVM system capability to check if TDX VM type is supported
- VM creation (KVM_CREATE_VM)
- New: Get the TDX specific system parameters. KVM_TDX_GET_CAPABILITY.
- New: Set TDX specific VM parameters. KVM_TDX_INIT_VM.
- VCPU creation (KVM_CREATE_VCPU)
- New: Set TDX specific VCPU parameters. KVM_TDX_INIT_VCPU.
- New: Initialize guest memory as boot state and extend the measurement with the memory. KVM_TDX_INIT_MEM_REGION.
- New: Finalize VM. KVM_TDX_FINALIZE. Complete measurement of the initial TDX VM contents.
- VCPU RUN (KVM_VCPU_RUN)

- Protected guest state
Because the guest state (CPU state and guest memory) is protected, the KVM VMM can't operate on them. For example, accessing CPU registers, injecting exceptions, and accessing guest memory. Those operations are handled as silently ignored, returning zero or initial reset value when it's requested via KVM API ioctls.

VM/VCPU state and callbacks for TDX specific operations. Define tdx specific VM state and VCPU state instead of VMX ones. Redirect operations to TDX specific callbacks. "if (tdx) tdx_op() else vmx_op()".

Operations on the CPU state
silently ignore operations on the guest state. For example, the write to CPU registers is ignored and the read from CPU registers returns 0.

. ignore access to CPU registers except for allowed ones.
. TSC: add a check if tsc is immutable and return an error. Because the KVM
implementation updates the internal tsc state and it's difficult to back out those changes. Instead, skip the logic.
. dirty logging: add check if dirty logging is supported.
. exceptions/SMI/MCE/SIPI/INIT: silently ignore

Note: virtual external interrupt and NMI can be injected into TDX guests.

- KVM MMU integration
One bit of the guest physical address (bit 51 or 47) is repurposed to indicate if the guest physical address is private (the bit is cleared) or shared (the bit is set). The bits are called stolen bits.

- Stolen bits framework
systematically tracks which guest physical address, shared or private, is used.

- Shared EPT and secure EPT
There are two EPTs. Shared EPT (the conventional one) and Secure EPT(the new one). Shared EPT is handled the same for the stolen bit set. Secure EPT points to private guest pages. To resolve EPT violation, KVM walks one of two EPTs based on faulted GPA. Because it's costly to access secure EPT during walking EPTs with SEAMCALLs for the private guest physical address, another private EPT is used as a shadow of Secure-EPT with the existing logic at the cost of extra memory.

The following depicts the relationship.

                    KVM                             |       TDX module
                     |                              |           |
        -------------+----------                    |           |
        |                      |                    |           |
        V                      V                    |           |
     shared GPA           private GPA               |           |
  CPU shared EPT pointer  KVM private EPT pointer   |  CPU secure EPT pointer
        |                      |                    |           |
        |                      |                    |           |
        V                      V                    |           V
  shared EPT                private EPT--------mirror----->Secure EPT
        |                      |                    |           |
        |                      \--------------------+------\    |
        |                                           |      |    |
        V                                           |      V    V
  shared guest page                                 |    private guest page
                                                    |
                                                    |
                              non-encrypted memory  |    encrypted memory
                                                    |

- Operating on Secure EPT
Use the TDX module APIs to operate on Secure EPT. To call the TDX API during resolving EPT violation, add hooks to additional operation and wiring it to TDX backend.

* References

[1] TDX specification
https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html
[2] Intel Trust Domain Extensions (Intel TDX)
https://cdrdv2.intel.com/v1/dl/getContent/726790
[3] Intel CPU Architectural Extensions Specification
https://www.intel.com/content/dam/develop/external/us/en/documents-tps/intel-tdx-cpu-architectural-specification.pdf
[4] Intel TDX Module 1.0 Specification
https://www.intel.com/content/dam/develop/external/us/en/documents/tdx-module-1.0-public-spec-v0.931.pdf
[5] Intel TDX Loader Interface Specification
https://www.intel.com/content/dam/develop/external/us/en/documents-tps/intel-tdx-seamldr-interface-specification.pdf
[6] Intel TDX Guest-Hypervisor Communication Interface
https://cdrdv2.intel.com/v1/dl/getContent/726790
[7] Intel TDX Virtual Firmware Design Guide
https://www.intel.com/content/dam/develop/external/us/en/documents/tdx-virtual-firmware-design-guide-rev-1.01.pdf
[8] intel public github
kvm TDX branch: https://github.com/intel/tdx/tree/kvm
TDX guest branch: https://github.com/intel/tdx/tree/guest
qemu TDX https://github.com/intel/qemu-tdx
[9] TDVF
https://github.com/tianocore/edk2-staging/tree/TDVF
This was merged into EDK2 main branch. https://github.com/tianocore/edk2

Chao Gao (2):
KVM: x86/mmu: Assume guest MMIOs are shared
KVM: x86: Allow to update cached values in kvm_user_return_msrs w/o
wrmsr

Isaku Yamahata (101):
x86/tdx: Warning with 32bit build shift-count-overflow
KVM: x86: Pass is_private to gmem hook of gmem_max_level
KVM: Add new members to struct kvm_gfn_range to operate on
KVM: x86/mmu: Pass around full 64-bit error code for the KVM page
fault
KVM: x86: Use PFERR_GUEST_ENC_MASK to indicate fault is private
KVM: Add KVM vcpu ioctl to pre-populate guest memory
KVM: Document KVM_MEMORY_MAPPING ioctl
KVM: x86: Implement kvm_arch_{, pre_}vcpu_memory_mapping()
KVM: x86: Add is_vm_type_supported callback
KVM: x86/vmx: initialize loaded_vmcss_on_cpu in vmx_init()
KVM: x86/vmx: Refactor KVM VMX module init/exit functions
KVM: TDX: Initialize the TDX module when loading the KVM intel kernel
module
KVM: TDX: Add placeholders for TDX VM/vcpu structure
KVM: TDX: Make TDX VM type supported
[MARKER] The start of TDX KVM patch series: TDX architectural
definitions
KVM: TDX: Define TDX architectural definitions
KVM: TDX: Add C wrapper functions for SEAMCALLs to the TDX module
KVM: TDX: Add helper functions to print TDX SEAMCALL error [MARKER] The start of TDX KVM patch series: TD VM creation/destruction
KVM: TDX: Add helper functions to allocate/free TDX private host key
id
KVM: TDX: Add helper function to read TDX metadata in array
KVM: TDX: Get system-wide info about TDX module on initialization
KVM: TDX: Add place holder for TDX VM specific mem_enc_op ioctl
KVM: TDX: Make KVM_CAP_MAX_VCPUS backend specific
KVM: TDX: create/destroy VM structure
KVM: TDX: initialize VM with TDX specific parameters
KVM: TDX: Make pmu_intel.c ignore guest TD case
KVM: TDX: Refuse to unplug the last cpu on the package [MARKER] The start of TDX KVM patch series: TD vcpu
creation/destruction
KVM: TDX: create/free TDX vcpu structure
KVM: TDX: Do TDX specific vcpu initialization
[MARKER] The start of TDX KVM patch series: KVM MMU GPA shared bits
KVM: x86/mmu: Add address conversion functions for TDX shared bit of
GPA
[MARKER] The start of TDX KVM patch series: KVM TDP refactoring for
TDX
KVM: x86/mmu: Replace hardcoded value 0 for the initial value for SPTE
KVM: x86/mmu: Add Suppress VE bit to
shadow_mmio_mask/shadow_present_mask
KVM: x86/mmu: Track shadow MMIO value on a per-VM basis
KVM: x86/mmu: Disallow fast page fault on private GPA
KVM: VMX: Introduce test mode related to EPT violation VE [MARKER] The start of TDX KVM patch series: KVM TDP MMU hooks
KVM: x86/tdp_mmu: Init role member of struct kvm_mmu_page at
allocation
KVM: x86/mmu: Add a new is_private member for union kvm_mmu_page_role
KVM: x86/mmu: Add a private pointer to struct kvm_mmu_page
KVM: x86/tdp_mmu: Apply mmu notifier callback to only shared GPA
KVM: x86/tdp_mmu: Sprinkle __must_check
KVM: x86/tdp_mmu: Support TDX private mapping for TDP MMU [MARKER] The start of TDX KVM patch series: TDX EPT violation
KVM: TDX: Add accessors VMX VMCS helpers
KVM: TDX: Require TDP MMU and mmio caching for TDX
KVM: TDX: TDP MMU TDX support
KVM: TDX: MTRR: implement get_mt_mask() for TDX
[MARKER] The start of TDX KVM patch series: TD finalization
KVM: x86: Add hooks in kvm_arch_vcpu_memory_mapping()
KVM: TDX: Create initial guest memory
KVM: TDX: Extend memory measurement with initial guest memory
KVM: TDX: Finalize VM initialization
[MARKER] The start of TDX KVM patch series: TD vcpu enter/exit
KVM: TDX: Implement TDX vcpu enter/exit path
KVM: TDX: vcpu_run: save/restore host state(host kernel gs)
KVM: TDX: restore host xsave state when exit from the guest TD
KVM: TDX: restore user ret MSRs
[MARKER] The start of TDX KVM patch series: TD vcpu
exits/interrupts/hypercalls
KVM: TDX: Complete interrupts after tdexit
KVM: TDX: restore debug store when TD exit
KVM: TDX: handle vcpu migration over logical processor
KVM: x86: Add a switch_db_regs flag to handle TDX's auto-switched
behavior
KVM: TDX: remove use of struct vcpu_vmx from posted_interrupt.c
KVM: TDX: Implement interrupt injection
KVM: TDX: Implements vcpu request_immediate_exit
KVM: TDX: Implement methods to inject NMI
KVM: TDX: Add a place holder to handle TDX VM exit
KVM: TDX: handle EXIT_REASON_OTHER_SMI
KVM: TDX: handle ept violation/misconfig exit
KVM: TDX: handle EXCEPTION_NMI and EXTERNAL_INTERRUPT
KVM: TDX: Handle EXIT_REASON_OTHER_SMI with MSMI
KVM: TDX: Add a place holder for handler of TDX hypercalls

    (TDG.VP.VMCALL)

KVM: TDX: handle KVM hypercall with TDG.VP.VMCALL
KVM: TDX: Add KVM Exit for TDX TDG.VP.VMCALL
KVM: TDX: Handle TDX PV CPUID hypercall
KVM: TDX: Handle TDX PV HLT hypercall
KVM: TDX: Handle TDX PV port io hypercall
KVM: TDX: Implement callbacks for MSR operations for TDX
KVM: TDX: Handle TDX PV rdmsr/wrmsr hypercall
KVM: TDX: Handle MSR MTRRCap and MTRRDefType access
KVM: TDX: Handle MSR IA32_FEAT_CTL MSR and IA32_MCG_EXT_CTL
KVM: TDX: Handle TDG.VP.VMCALL<GetTdVmCallInfo> hypercall
KVM: TDX: Silently discard SMI request
KVM: TDX: Silently ignore INIT/SIPI
KVM: TDX: Add methods to ignore accesses to CPU state
KVM: TDX: Add methods to ignore guest instruction emulation
KVM: TDX: Add a method to ignore dirty logging
KVM: TDX: Add methods to ignore VMX preemption timer
KVM: TDX: Add methods to ignore accesses to TSC
KVM: TDX: Ignore setting up mce
KVM: TDX: Add a method to ignore for TDX to ignore hypercall patch
KVM: TDX: Add methods to ignore virtual apic related operation
KVM: TDX: Inhibit APICv for TDX guest Documentation/virt/kvm: Document on Trust Domain Extensions(TDX)
KVM: x86: design documentation on TDX support of x86 KVM TDP MMU
RFC: KVM: x86: Add x86 callback to check cpuid
RFC: KVM: x86, TDX: Add check for KVM_SET_CPUID2

Kai Huang (7):
x86/virt/tdx: Rename _offset to _member for TD_SYSINFO_MAP() macro x86/virt/tdx: Move TDMR metadata fields map table to local variable x86/virt/tdx: Unbind global metadata read with 'struct
tdx_tdmr_sysinfo'
x86/virt/tdx: Support global metadata read for all element sizes x86/virt/tdx: Export global metadata read infrastructure x86/virt/tdx: Export TDX KeyID information
x86/virt/tdx: Export SEAMCALL functions

Michael Roth (1):
KVM: x86: Add gmem hook for determining max NPT mapping level

Sean Christopherson (15):
KVM: x86/mmu: Introduce kvm_mmu_map_tdp_page() for use by TDX
KVM: VMX: Move out vmx_x86_ops to 'main.c' to wrap VMX and TDX
KVM: TDX: Add TDX "architectural" error codes
KVM: TDX: x86: Add ioctl to get TDX systemwide parameters
KVM: Allow page-sized MMU caches to be initialized with custom 64-bit
values
KVM: x86/mmu: Allow non-zero value for non-present SPTE and removed
SPTE
KVM: x86/tdp_mmu: Don't zap private pages for unsupported cases
KVM: VMX: Split out guts of EPT violation to common/exposed function
KVM: TDX: Add load_mmu_pgd method for TDX
KVM: TDX: Add support for find pending IRQ in a protected local APIC
KVM: x86: Assume timer IRQ was injected if APIC state is proteced
KVM: VMX: Modify NMI and INTR handlers to take intr_info as function
argument
KVM: VMX: Move NMI/exception handler to common helper
KVM: x86: Split core of hypercall emulation to helper function
KVM: TDX: Handle TDX PV MMIO hypercall

Yan Zhao (1):
KVM: x86/mmu: Do not enable page track for TD guest

Yang Weijiang (1):
KVM: TDX: Add TSX_CTRL msr into uret_msrs list

Yao Yuan (1):
KVM: TDX: Handle vmentry failure for INTEL TD guest

Yuan Yao (1):
KVM: TDX: Retry seamcall when TDX_OPERAND_BUSY with operand SEPT

 Documentation/virt/kvm/api.rst                |   37 +-
 Documentation/virt/kvm/index.rst              |    2 +
 .../virt/kvm/intel-tdx-layer-status.rst       |   33 +
 Documentation/virt/kvm/x86/index.rst          |    2 +
 Documentation/virt/kvm/x86/intel-tdx.rst      |  362 ++
 Documentation/virt/kvm/x86/tdx-tdp-mmu.rst    |  443 +++
 arch/x86/events/intel/ds.c                    |    1 +
 arch/x86/include/asm/asm-prototypes.h         |    1 +
 arch/x86/include/asm/kvm-x86-ops.h            |   21 +-
 arch/x86/include/asm/kvm_host.h               |   79 +-
 arch/x86/include/asm/shared/tdx.h             |    9 +-
 arch/x86/include/asm/tdx.h                    |   31 +-
 arch/x86/include/asm/vmx.h                    |   14 +
 arch/x86/include/uapi/asm/kvm.h               |   86 +
 arch/x86/include/uapi/asm/vmx.h               |    5 +-
 arch/x86/kvm/Kconfig                          |    3 +-
 arch/x86/kvm/Makefile                         |    3 +-
 arch/x86/kvm/cpuid.c                          |   27 +-
 arch/x86/kvm/cpuid.h                          |    2 +
 arch/x86/kvm/irq.c                            |    3 +
 arch/x86/kvm/lapic.c                          |   33 +-
 arch/x86/kvm/lapic.h                          |    2 +
 arch/x86/kvm/mmu.h                            |   37 +
 arch/x86/kvm/mmu/mmu.c                        |  247 +-
 arch/x86/kvm/mmu/mmu_internal.h               |   87 +-
 arch/x86/kvm/mmu/mmutrace.h                   |    2 +-
 arch/x86/kvm/mmu/page_track.c                 |    3 +
 arch/x86/kvm/mmu/paging_tmpl.h                |    2 +-
 arch/x86/kvm/mmu/spte.c                       |   17 +-
 arch/x86/kvm/mmu/spte.h                       |   28 +-
 arch/x86/kvm/mmu/tdp_iter.h                   |   14 +-
 arch/x86/kvm/mmu/tdp_mmu.c                    |  442 ++-
 arch/x86/kvm/mmu/tdp_mmu.h                    |    7 +-
 arch/x86/kvm/smm.h                            |    7 +-
 arch/x86/kvm/svm/svm.c                        |    8 +
 arch/x86/kvm/vmx/common.h                     |  166 +
 arch/x86/kvm/vmx/main.c                       | 1268 +++++++
 arch/x86/kvm/vmx/pmu_intel.c                  |   46 +-
 arch/x86/kvm/vmx/pmu_intel.h                  |   28 +
 arch/x86/kvm/vmx/posted_intr.c                |   43 +-
 arch/x86/kvm/vmx/posted_intr.h                |   13 +
 arch/x86/kvm/vmx/tdx.c                        | 3347 +++++++++++++++++
 arch/x86/kvm/vmx/tdx.h                        |  268 ++
 arch/x86/kvm/vmx/tdx_arch.h                   |  273 ++
 arch/x86/kvm/vmx/tdx_errno.h                  |   34 +
 arch/x86/kvm/vmx/tdx_error.c                  |   21 +
 arch/x86/kvm/vmx/tdx_ops.h                    |  400 ++
 arch/x86/kvm/vmx/vmcs.h                       |    5 +
 arch/x86/kvm/vmx/vmx.c                        |  663 +---
 arch/x86/kvm/vmx/vmx.h                        |   52 +-
 arch/x86/kvm/vmx/x86_ops.h                    |  276 ++
 arch/x86/kvm/x86.c                            |  166 +-
 arch/x86/kvm/x86.h                            |    4 +
 arch/x86/virt/vmx/tdx/seamcall.S              |    4 +
 arch/x86/virt/vmx/tdx/tdx.c                   |   95 +-
 arch/x86/virt/vmx/tdx/tdx.h                   |    2 -
 include/linux/kvm_host.h                      |    7 +
 include/linux/kvm_types.h                     |    1 +
 include/uapi/linux/kvm.h                      |   99 +
 virt/kvm/guest_memfd.c                        |    3 +
 virt/kvm/kvm_main.c                           |  115 +-
 61 files changed, 8741 insertions(+), 758 deletions(-)
 create mode 100644 Documentation/virt/kvm/intel-tdx-layer-status.rst
 create mode 100644 Documentation/virt/kvm/x86/intel-tdx.rst
 create mode 100644 Documentation/virt/kvm/x86/tdx-tdp-mmu.rst
 create mode 100644 arch/x86/kvm/vmx/common.h
 create mode 100644 arch/x86/kvm/vmx/main.c
 create mode 100644 arch/x86/kvm/vmx/pmu_intel.h
 create mode 100644 arch/x86/kvm/vmx/tdx.c
 create mode 100644 arch/x86/kvm/vmx/tdx.h
 create mode 100644 arch/x86/kvm/vmx/tdx_arch.h
 create mode 100644 arch/x86/kvm/vmx/tdx_errno.h
 create mode 100644 arch/x86/kvm/vmx/tdx_error.c
 create mode 100644 arch/x86/kvm/vmx/tdx_ops.h
 create mode 100644 arch/x86/kvm/vmx/x86_ops.h

base-commit: ffd2cb6b718e189e7e2d5d0c19c25611f92e061a

Signature · 1 line
2.25.1
#1 lore raw

PATCH v19 1/130 x86/virt/tdx: Rename _offset to _member for TD_SYSINFO_MAP() macro

From: Kai Huang <[email protected]>

TD_SYSINFO_MAP() macro actually takes the member of the 'struct tdx_tdmr_sysinfo' as the second argument and uses the offsetof() to calculate the offset for that member.

Rename the macro argument _offset to _member to reflect this.

---
 arch/x86/virt/vmx/tdx/tdx.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
arch/x86/virt/vmx/tdx/tdx.c +2 -2
index 4d6826a76f78..2aee64d2f27f 100644
@@ -297,9 +297,9 @@ struct field_mapping {
 	int offset;
 };
 
-#define TD_SYSINFO_MAP(_field_id, _offset) \
+#define TD_SYSINFO_MAP(_field_id, _member) \
 	{ .field_id = MD_FIELD_ID_##_field_id,	   \
-	  .offset   = offsetof(struct tdx_tdmr_sysinfo, _offset) }
+	  .offset   = offsetof(struct tdx_tdmr_sysinfo, _member) }
 
 /* Map TD_SYSINFO fields into 'struct tdx_tdmr_sysinfo': */
 static const struct field_mapping fields[] = {
Signature · 1 line
2.25.1
Reviewed-by on v19 1/130
#2 lore raw

Re: [PATCH v19 001/130] x86/virt/tdx: Rename _offset to _member for TD_SYSINFO_MAP() macro

Quoted 11 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Kai Huang <[email protected]>

TD_SYSINFO_MAP() macro actually takes the member of the 'struct tdx_tdmr_sysinfo' as the second argument and uses the offsetof() to calculate the offset for that member.

Rename the macro argument _offset to _member to reflect this.

Quoted 20 lines
---
  arch/x86/virt/vmx/tdx/tdx.c | 4 ++--
  1 file changed, 2 insertions(+), 2 deletions(-)
arch/x86/virt/vmx/tdx/tdx.c +2 -2
index 4d6826a76f78..2aee64d2f27f 100644
@@ -297,9 +297,9 @@ struct field_mapping {
  	int offset;
  };
 
-#define TD_SYSINFO_MAP(_field_id, _offset) \
+#define TD_SYSINFO_MAP(_field_id, _member) \
  	{ .field_id = MD_FIELD_ID_##_field_id,	   \
-	  .offset   = offsetof(struct tdx_tdmr_sysinfo, _offset) }
+	  .offset   = offsetof(struct tdx_tdmr_sysinfo, _member) }
 
  /* Map TD_SYSINFO fields into 'struct tdx_tdmr_sysinfo': */
  static const struct field_mapping fields[] = {
#3 lore raw

Re: [PATCH v19 001/130] x86/virt/tdx: Rename _offset to _member for TD_SYSINFO_MAP() macro

Quoted 8 lines · Binbin Wu
Quoted 6 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Kai Huang <[email protected]>

TD_SYSINFO_MAP() macro actually takes the member of the 'struct tdx_tdmr_sysinfo' as the second argument and uses the offsetof() to calculate the offset for that member.

Quoted 9 lines
Quoted 5 lines

Rename the macro argument _offset to _member to reflect this.

Thanks for review.

I've sent out the first 5 metadata read patches as a standalone patchset separately:

https://lore.kernel.org/kvm/[email protected]/

I'll add your Reviewed-by in next version.

(for others please comment there instead, thanks!)

#4 lore raw

PATCH v19 2/130 x86/virt/tdx: Move TDMR metadata fields map table to local variable

In reply to [email protected]

From: Kai Huang <[email protected]>

The kernel reads all TDMR related global metadata fields based on a table which maps the metadata fields to the corresponding members of 'struct tdx_tdmr_sysinfo'. Currently this table is a static variable.

But this table is only used by the function which reads these metadata fields and becomes useless after reading is done. Change the table to function local variable.

---
 arch/x86/virt/vmx/tdx/tdx.c | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)
arch/x86/virt/vmx/tdx/tdx.c +8 -9
index 2aee64d2f27f..cdcb3332bc5d 100644
@@ -301,17 +301,16 @@ struct field_mapping {
 	{ .field_id = MD_FIELD_ID_##_field_id,	   \
 	  .offset   = offsetof(struct tdx_tdmr_sysinfo, _member) }
 
-/* Map TD_SYSINFO fields into 'struct tdx_tdmr_sysinfo': */
-static const struct field_mapping fields[] = {
-	TD_SYSINFO_MAP(MAX_TDMRS,	      max_tdmrs),
-	TD_SYSINFO_MAP(MAX_RESERVED_PER_TDMR, max_reserved_per_tdmr),
-	TD_SYSINFO_MAP(PAMT_4K_ENTRY_SIZE,    pamt_entry_size[TDX_PS_4K]),
-	TD_SYSINFO_MAP(PAMT_2M_ENTRY_SIZE,    pamt_entry_size[TDX_PS_2M]),
-	TD_SYSINFO_MAP(PAMT_1G_ENTRY_SIZE,    pamt_entry_size[TDX_PS_1G]),
-};
-
 static int get_tdx_tdmr_sysinfo(struct tdx_tdmr_sysinfo *tdmr_sysinfo)
 {
+	/* Map TD_SYSINFO fields into 'struct tdx_tdmr_sysinfo': */
+	const struct field_mapping fields[] = {
+		TD_SYSINFO_MAP(MAX_TDMRS,	      max_tdmrs),
+		TD_SYSINFO_MAP(MAX_RESERVED_PER_TDMR, max_reserved_per_tdmr),
+		TD_SYSINFO_MAP(PAMT_4K_ENTRY_SIZE,    pamt_entry_size[TDX_PS_4K]),
+		TD_SYSINFO_MAP(PAMT_2M_ENTRY_SIZE,    pamt_entry_size[TDX_PS_2M]),
+		TD_SYSINFO_MAP(PAMT_1G_ENTRY_SIZE,    pamt_entry_size[TDX_PS_1G]),
+	};
 	int ret;
 	int i;
 
Signature · 1 line
2.25.1
Reviewed-by on v19 2/130
#5 lore raw

Re: [PATCH v19 002/130] x86/virt/tdx: Move TDMR metadata fields map table to local variable

Quoted 6 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Kai Huang <[email protected]>

The kernel reads all TDMR related global metadata fields based on a table which maps the metadata fields to the corresponding members of 'struct tdx_tdmr_sysinfo'. Currently this table is a static variable.

Quoted 6 lines

But this table is only used by the function which reads these metadata fields and becomes useless after reading is done. Change the table to function local variable.

Quoted 34 lines
---
  arch/x86/virt/vmx/tdx/tdx.c | 17 ++++++++---------
  1 file changed, 8 insertions(+), 9 deletions(-)
arch/x86/virt/vmx/tdx/tdx.c +8 -9
index 2aee64d2f27f..cdcb3332bc5d 100644
@@ -301,17 +301,16 @@ struct field_mapping {
  	{ .field_id = MD_FIELD_ID_##_field_id,	   \
  	  .offset   = offsetof(struct tdx_tdmr_sysinfo, _member) }
 
-/* Map TD_SYSINFO fields into 'struct tdx_tdmr_sysinfo': */
-static const struct field_mapping fields[] = {
-	TD_SYSINFO_MAP(MAX_TDMRS,	      max_tdmrs),
-	TD_SYSINFO_MAP(MAX_RESERVED_PER_TDMR, max_reserved_per_tdmr),
-	TD_SYSINFO_MAP(PAMT_4K_ENTRY_SIZE,    pamt_entry_size[TDX_PS_4K]),
-	TD_SYSINFO_MAP(PAMT_2M_ENTRY_SIZE,    pamt_entry_size[TDX_PS_2M]),
-	TD_SYSINFO_MAP(PAMT_1G_ENTRY_SIZE,    pamt_entry_size[TDX_PS_1G]),
-};
-
  static int get_tdx_tdmr_sysinfo(struct tdx_tdmr_sysinfo *tdmr_sysinfo)
  {
+	/* Map TD_SYSINFO fields into 'struct tdx_tdmr_sysinfo': */
+	const struct field_mapping fields[] = {
+		TD_SYSINFO_MAP(MAX_TDMRS,	      max_tdmrs),
+		TD_SYSINFO_MAP(MAX_RESERVED_PER_TDMR, max_reserved_per_tdmr),
+		TD_SYSINFO_MAP(PAMT_4K_ENTRY_SIZE,    pamt_entry_size[TDX_PS_4K]),
+		TD_SYSINFO_MAP(PAMT_2M_ENTRY_SIZE,    pamt_entry_size[TDX_PS_2M]),
+		TD_SYSINFO_MAP(PAMT_1G_ENTRY_SIZE,    pamt_entry_size[TDX_PS_1G]),
+	};
  	int ret;
  	int i;
 
Reviewed-by on v19 2/130
#6 lore raw

Re: [PATCH v19 002/130] x86/virt/tdx: Move TDMR metadata fields map table to local variable

In reply to [email protected]
Quoted 6 lines · On Mon, Feb 26, 2024 at 12:25:04AM -0800, [email protected] wrote:

From: Kai Huang <[email protected]>

The kernel reads all TDMR related global metadata fields based on a table which maps the metadata fields to the corresponding members of 'struct tdx_tdmr_sysinfo'. Currently this table is a static variable.

Quoted 6 lines

But this table is only used by the function which reads these metadata fields and becomes useless after reading is done. Change the table to function local variable.

Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov
#7 lore raw

Re: [PATCH v19 002/130] x86/virt/tdx: Move TDMR metadata fields map table to local variable

Quoted 7 lines · Kirill A. Shutemov
Quoted 6 lines · On Mon, Feb 26, 2024 at 12:25:04AM -0800, [email protected] wrote:

From: Kai Huang <[email protected]>

The kernel reads all TDMR related global metadata fields based on a table which maps the metadata fields to the corresponding members of 'struct tdx_tdmr_sysinfo'. Currently this table is a static variable.

Quoted 9 lines
Quoted 6 lines

But this table is only used by the function which reads these metadata fields and becomes useless after reading is done. Change the table to function local variable.

Thanks Kirill. I've split this out and sent out v2, and have added your tag there:

https://lore.kernel.org/lkml/[email protected]/

#8 lore raw

PATCH v19 3/130 x86/virt/tdx: Unbind global metadata read with 'struct tdx_tdmr_sysinfo'

In reply to [email protected]

From: Kai Huang <[email protected]>

For now the kernel only reads TDMR related global metadata fields for module initialization, and the metadata read code only works with the 'struct tdx_tdmr_sysinfo'.

KVM will need to read a bunch of non-TDMR related metadata to create and run TDX guests. It's essential to provide a generic metadata read infrastructure which is not bound to any specific structure.

To start providing such infrastructure, unbound the metadata read with the 'struct tdx_tdmr_sysinfo'.

---
 arch/x86/virt/vmx/tdx/tdx.c | 25 ++++++++++++++-----------
 1 file changed, 14 insertions(+), 11 deletions(-)
arch/x86/virt/vmx/tdx/tdx.c +14 -11
index cdcb3332bc5d..eb208da4ff63 100644
@@ -273,9 +273,9 @@ static int read_sys_metadata_field(u64 field_id, u64 *data)
 
 static int read_sys_metadata_field16(u64 field_id,
 				     int offset,
-				     struct tdx_tdmr_sysinfo *ts)
+				     void *stbuf)
 {
-	u16 *ts_member = ((void *)ts) + offset;
+	u16 *st_member = stbuf + offset;
 	u64 tmp;
 	int ret;
 
@@ -287,7 +287,7 @@ static int read_sys_metadata_field16(u64 field_id,
 	if (ret)
 		return ret;
 
-	*ts_member = tmp;
+	*st_member = tmp;
 
 	return 0;
 }
@@ -297,19 +297,22 @@ struct field_mapping {
 	int offset;
 };
 
-#define TD_SYSINFO_MAP(_field_id, _member) \
-	{ .field_id = MD_FIELD_ID_##_field_id,	   \
-	  .offset   = offsetof(struct tdx_tdmr_sysinfo, _member) }
+#define TD_SYSINFO_MAP(_field_id, _struct, _member)	\
+	{ .field_id = MD_FIELD_ID_##_field_id,		\
+	  .offset   = offsetof(_struct, _member) }
+
+#define TD_SYSINFO_MAP_TDMR_INFO(_field_id, _member)	\
+	TD_SYSINFO_MAP(_field_id, struct tdx_tdmr_sysinfo, _member)
 
 static int get_tdx_tdmr_sysinfo(struct tdx_tdmr_sysinfo *tdmr_sysinfo)
 {
 	/* Map TD_SYSINFO fields into 'struct tdx_tdmr_sysinfo': */
 	const struct field_mapping fields[] = {
-		TD_SYSINFO_MAP(MAX_TDMRS,	      max_tdmrs),
-		TD_SYSINFO_MAP(MAX_RESERVED_PER_TDMR, max_reserved_per_tdmr),
-		TD_SYSINFO_MAP(PAMT_4K_ENTRY_SIZE,    pamt_entry_size[TDX_PS_4K]),
-		TD_SYSINFO_MAP(PAMT_2M_ENTRY_SIZE,    pamt_entry_size[TDX_PS_2M]),
-		TD_SYSINFO_MAP(PAMT_1G_ENTRY_SIZE,    pamt_entry_size[TDX_PS_1G]),
+		TD_SYSINFO_MAP_TDMR_INFO(MAX_TDMRS,		max_tdmrs),
+		TD_SYSINFO_MAP_TDMR_INFO(MAX_RESERVED_PER_TDMR, max_reserved_per_tdmr),
+		TD_SYSINFO_MAP_TDMR_INFO(PAMT_4K_ENTRY_SIZE,    pamt_entry_size[TDX_PS_4K]),
+		TD_SYSINFO_MAP_TDMR_INFO(PAMT_2M_ENTRY_SIZE,    pamt_entry_size[TDX_PS_2M]),
+		TD_SYSINFO_MAP_TDMR_INFO(PAMT_1G_ENTRY_SIZE,    pamt_entry_size[TDX_PS_1G]),
 	};
 	int ret;
 	int i;
Signature · 1 line
2.25.1
Reviewed-by on v19 3/130
#9 lore raw

Re: [PATCH v19 003/130] x86/virt/tdx: Unbind global metadata read with 'struct tdx_tdmr_sysinfo'

Quoted 10 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Kai Huang <[email protected]>

For now the kernel only reads TDMR related global metadata fields for module initialization, and the metadata read code only works with the 'struct tdx_tdmr_sysinfo'.

KVM will need to read a bunch of non-TDMR related metadata to create and run TDX guests. It's essential to provide a generic metadata read infrastructure which is not bound to any specific structure.

Quoted 6 lines

To start providing such infrastructure, unbound the metadata read with the 'struct tdx_tdmr_sysinfo'.

Quoted 60 lines
---
  arch/x86/virt/vmx/tdx/tdx.c | 25 ++++++++++++++-----------
  1 file changed, 14 insertions(+), 11 deletions(-)
arch/x86/virt/vmx/tdx/tdx.c +14 -11
index cdcb3332bc5d..eb208da4ff63 100644
@@ -273,9 +273,9 @@ static int read_sys_metadata_field(u64 field_id, u64 *data)
 
  static int read_sys_metadata_field16(u64 field_id,
  				     int offset,
-				     struct tdx_tdmr_sysinfo *ts)
+				     void *stbuf)
  {
-	u16 *ts_member = ((void *)ts) + offset;
+	u16 *st_member = stbuf + offset;
  	u64 tmp;
  	int ret;
 
@@ -287,7 +287,7 @@ static int read_sys_metadata_field16(u64 field_id,
  	if (ret)
  		return ret;
 
-	*ts_member = tmp;
+	*st_member = tmp;
 
  	return 0;
  }
@@ -297,19 +297,22 @@ struct field_mapping {
  	int offset;
  };
 
-#define TD_SYSINFO_MAP(_field_id, _member) \
-	{ .field_id = MD_FIELD_ID_##_field_id,	   \
-	  .offset   = offsetof(struct tdx_tdmr_sysinfo, _member) }
+#define TD_SYSINFO_MAP(_field_id, _struct, _member)	\
+	{ .field_id = MD_FIELD_ID_##_field_id,		\
+	  .offset   = offsetof(_struct, _member) }
+
+#define TD_SYSINFO_MAP_TDMR_INFO(_field_id, _member)	\
+	TD_SYSINFO_MAP(_field_id, struct tdx_tdmr_sysinfo, _member)
 
  static int get_tdx_tdmr_sysinfo(struct tdx_tdmr_sysinfo *tdmr_sysinfo)
  {
  	/* Map TD_SYSINFO fields into 'struct tdx_tdmr_sysinfo': */
  	const struct field_mapping fields[] = {
-		TD_SYSINFO_MAP(MAX_TDMRS,	      max_tdmrs),
-		TD_SYSINFO_MAP(MAX_RESERVED_PER_TDMR, max_reserved_per_tdmr),
-		TD_SYSINFO_MAP(PAMT_4K_ENTRY_SIZE,    pamt_entry_size[TDX_PS_4K]),
-		TD_SYSINFO_MAP(PAMT_2M_ENTRY_SIZE,    pamt_entry_size[TDX_PS_2M]),
-		TD_SYSINFO_MAP(PAMT_1G_ENTRY_SIZE,    pamt_entry_size[TDX_PS_1G]),
+		TD_SYSINFO_MAP_TDMR_INFO(MAX_TDMRS,		max_tdmrs),
+		TD_SYSINFO_MAP_TDMR_INFO(MAX_RESERVED_PER_TDMR, max_reserved_per_tdmr),
+		TD_SYSINFO_MAP_TDMR_INFO(PAMT_4K_ENTRY_SIZE,    pamt_entry_size[TDX_PS_4K]),
+		TD_SYSINFO_MAP_TDMR_INFO(PAMT_2M_ENTRY_SIZE,    pamt_entry_size[TDX_PS_2M]),
+		TD_SYSINFO_MAP_TDMR_INFO(PAMT_1G_ENTRY_SIZE,    pamt_entry_size[TDX_PS_1G]),
  	};
  	int ret;
  	int i;
#10 lore raw

PATCH v19 4/130 x86/virt/tdx: Support global metadata read for all element sizes

In reply to [email protected]

From: Kai Huang <[email protected]>

For now the kernel only reads TDMR related global metadata fields for module initialization. All these fields are 16-bits, and the kernel only supports reading 16-bits fields.

KVM will need to read a bunch of non-TDMR related metadata to create and run TDX guests. It's essential to provide a generic metadata read infrastructure which supports reading all 8/16/32/64 bits element sizes.

Extend the metadata read to support reading all these element sizes.

---
 arch/x86/virt/vmx/tdx/tdx.c | 59 +++++++++++++++++++++++++------------
 arch/x86/virt/vmx/tdx/tdx.h |  2 --
 2 files changed, 40 insertions(+), 21 deletions(-)
arch/x86/virt/vmx/tdx/tdx.c +40 -19
index eb208da4ff63..a19adc898df6 100644
@@ -271,23 +271,35 @@ static int read_sys_metadata_field(u64 field_id, u64 *data)
 	return 0;
 }
 
-static int read_sys_metadata_field16(u64 field_id,
-				     int offset,
-				     void *stbuf)
+/* Return the metadata field element size in bytes */
+static int get_metadata_field_bytes(u64 field_id)
 {
-	u16 *st_member = stbuf + offset;
+	/*
+	 * TDX supports 8/16/32/64 bits metadata field element sizes.
+	 * TDX module determines the metadata element size based on the
+	 * "element size code" encoded in the field ID (see the comment
+	 * of MD_FIELD_ID_ELE_SIZE_CODE macro for specific encodings).
+	 */
+	return 1 << MD_FIELD_ID_ELE_SIZE_CODE(field_id);
+}
+
+static int stbuf_read_sys_metadata_field(u64 field_id,
+					 int offset,
+					 int bytes,
+					 void *stbuf)
+{
+	void *st_member = stbuf + offset;
 	u64 tmp;
 	int ret;
 
-	if (WARN_ON_ONCE(MD_FIELD_ID_ELE_SIZE_CODE(field_id) !=
-			MD_FIELD_ID_ELE_SIZE_16BIT))
+	if (WARN_ON_ONCE(get_metadata_field_bytes(field_id) != bytes))
 		return -EINVAL;
 
 	ret = read_sys_metadata_field(field_id, &tmp);
 	if (ret)
 		return ret;
 
-	*st_member = tmp;
+	memcpy(st_member, &tmp, bytes);
 
 	return 0;
 }
@@ -295,11 +307,30 @@ static int read_sys_metadata_field16(u64 field_id,
 struct field_mapping {
 	u64 field_id;
 	int offset;
+	int size;
 };
 
 #define TD_SYSINFO_MAP(_field_id, _struct, _member)	\
 	{ .field_id = MD_FIELD_ID_##_field_id,		\
-	  .offset   = offsetof(_struct, _member) }
+	  .offset   = offsetof(_struct, _member),	\
+	  .size     = sizeof(typeof(((_struct *)0)->_member)) }
+
+static int read_sys_metadata(struct field_mapping *fields, int nr_fields,
+			     void *stbuf)
+{
+	int i, ret;
+
+	for (i = 0; i < nr_fields; i++) {
+		ret = stbuf_read_sys_metadata_field(fields[i].field_id,
+				      fields[i].offset,
+				      fields[i].size,
+				      stbuf);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
 
 #define TD_SYSINFO_MAP_TDMR_INFO(_field_id, _member)	\
 	TD_SYSINFO_MAP(_field_id, struct tdx_tdmr_sysinfo, _member)
@@ -314,19 +345,9 @@ static int get_tdx_tdmr_sysinfo(struct tdx_tdmr_sysinfo *tdmr_sysinfo)
 		TD_SYSINFO_MAP_TDMR_INFO(PAMT_2M_ENTRY_SIZE,    pamt_entry_size[TDX_PS_2M]),
 		TD_SYSINFO_MAP_TDMR_INFO(PAMT_1G_ENTRY_SIZE,    pamt_entry_size[TDX_PS_1G]),
 	};
-	int ret;
-	int i;
 
 	/* Populate 'tdmr_sysinfo' fields using the mapping structure above: */
-	for (i = 0; i < ARRAY_SIZE(fields); i++) {
-		ret = read_sys_metadata_field16(fields[i].field_id,
-						fields[i].offset,
-						tdmr_sysinfo);
-		if (ret)
-			return ret;
-	}
-
-	return 0;
+	return read_sys_metadata(fields, ARRAY_SIZE(fields), tdmr_sysinfo);
 }
 
 /* Calculate the actual TDMR size */
arch/x86/virt/vmx/tdx/tdx.h +0 -2
index b701f69485d3..4c32c8bf156a 100644
@@ -53,8 +53,6 @@
 #define MD_FIELD_ID_ELE_SIZE_CODE(_field_id)	\
 		(((_field_id) & GENMASK_ULL(33, 32)) >> 32)
 
-#define MD_FIELD_ID_ELE_SIZE_16BIT	1
-
 struct tdmr_reserved_area {
 	u64 offset;
 	u64 size;
Signature · 1 line
2.25.1
#11 lore raw

Re: [PATCH v19 004/130] x86/virt/tdx: Support global metadata read for all element sizes

Quoted 70 lines · On 26.02.24 09:25, [email protected] wrote:

From: Kai Huang <[email protected]>

For now the kernel only reads TDMR related global metadata fields for module initialization. All these fields are 16-bits, and the kernel only supports reading 16-bits fields.

KVM will need to read a bunch of non-TDMR related metadata to create and run TDX guests. It's essential to provide a generic metadata read infrastructure which supports reading all 8/16/32/64 bits element sizes.

Extend the metadata read to support reading all these element sizes.

---
  arch/x86/virt/vmx/tdx/tdx.c | 59 +++++++++++++++++++++++++------------
  arch/x86/virt/vmx/tdx/tdx.h |  2 --
  2 files changed, 40 insertions(+), 21 deletions(-)
arch/x86/virt/vmx/tdx/tdx.c +19 -7
index eb208da4ff63..a19adc898df6 100644
@@ -271,23 +271,35 @@ static int read_sys_metadata_field(u64 field_id, u64 *data)
  	return 0;
  }
 
-static int read_sys_metadata_field16(u64 field_id,
-				     int offset,
-				     void *stbuf)
+/* Return the metadata field element size in bytes */
+static int get_metadata_field_bytes(u64 field_id)
  {
-	u16 *st_member = stbuf + offset;
+	/*
+	 * TDX supports 8/16/32/64 bits metadata field element sizes.
+	 * TDX module determines the metadata element size based on the
+	 * "element size code" encoded in the field ID (see the comment
+	 * of MD_FIELD_ID_ELE_SIZE_CODE macro for specific encodings).
+	 */
+	return 1 << MD_FIELD_ID_ELE_SIZE_CODE(field_id);
+}
+
+static int stbuf_read_sys_metadata_field(u64 field_id,
+					 int offset,
+					 int bytes,
+					 void *stbuf)
+{
+	void *st_member = stbuf + offset;
  	u64 tmp;
  	int ret;
 
-	if (WARN_ON_ONCE(MD_FIELD_ID_ELE_SIZE_CODE(field_id) !=
-			MD_FIELD_ID_ELE_SIZE_16BIT))
+	if (WARN_ON_ONCE(get_metadata_field_bytes(field_id) != bytes))
  		return -EINVAL;
 
  	ret = read_sys_metadata_field(field_id, &tmp);
  	if (ret)
  		return ret;
 
-	*st_member = tmp;
+	memcpy(st_member, &tmp, bytes);
 
  	return 0;
  }
@@ -295,11 +307,30 @@ static int read_sys_metadata_field16(u64 field_id,
  struct field_mapping {
  	u64 field_id;
  	int offset;

arch/x86/virt/vmx/tdx/tdx.c static int read_sys_metadata_field16(u64 field_id, · line 318

Quoted 10 lines
arch/x86/virt/vmx/tdx/tdx.c +5 -1
+	int size;
  };
 
  #define TD_SYSINFO_MAP(_field_id, _struct, _member)	\
  	{ .field_id = MD_FIELD_ID_##_field_id,		\
-	  .offset   = offsetof(_struct, _member) }
+	  .offset   = offsetof(_struct, _member),	\
+	  .size     = sizeof(typeof(((_struct *)0)->_member)) }
+
+static int read_sys_metadata(struct field_mapping *fields, int nr_fields,

The first parameter should be "const struct field_mapping *fields".

Juergen

#12 lore raw

Re: [PATCH v19 004/130] x86/virt/tdx: Support global metadata read for all element sizes

Quoted 74 lines · Juergen Gross
Quoted 73 lines · On 26.02.24 09:25, [email protected] wrote:

From: Kai Huang <[email protected]>

For now the kernel only reads TDMR related global metadata fields for module initialization. All these fields are 16-bits, and the kernel only supports reading 16-bits fields.

KVM will need to read a bunch of non-TDMR related metadata to create and run TDX guests. It's essential to provide a generic metadata read infrastructure which supports reading all 8/16/32/64 bits element sizes.

Extend the metadata read to support reading all these element sizes.

---
  arch/x86/virt/vmx/tdx/tdx.c | 59 +++++++++++++++++++++++++------------
  arch/x86/virt/vmx/tdx/tdx.h |  2 --
  2 files changed, 40 insertions(+), 21 deletions(-)
arch/x86/virt/vmx/tdx/tdx.c +20 -7
index eb208da4ff63..a19adc898df6 100644
@@ -271,23 +271,35 @@ static int read_sys_metadata_field(u64 field_id, u64 *data)
  	return 0;
  }
 
-static int read_sys_metadata_field16(u64 field_id,
-				     int offset,
-				     void *stbuf)
+/* Return the metadata field element size in bytes */
+static int get_metadata_field_bytes(u64 field_id)
  {
-	u16 *st_member = stbuf + offset;
+	/*
+	 * TDX supports 8/16/32/64 bits metadata field element sizes.
+	 * TDX module determines the metadata element size based on the
+	 * "element size code" encoded in the field ID (see the comment
+	 * of MD_FIELD_ID_ELE_SIZE_CODE macro for specific encodings).
+	 */
+	return 1 << MD_FIELD_ID_ELE_SIZE_CODE(field_id);
+}
+
+static int stbuf_read_sys_metadata_field(u64 field_id,
+					 int offset,
+					 int bytes,
+					 void *stbuf)
+{
+	void *st_member = stbuf + offset;
  	u64 tmp;
  	int ret;
 
-	if (WARN_ON_ONCE(MD_FIELD_ID_ELE_SIZE_CODE(field_id) !=
-			MD_FIELD_ID_ELE_SIZE_16BIT))
+	if (WARN_ON_ONCE(get_metadata_field_bytes(field_id) != bytes))
  		return -EINVAL;
 
  	ret = read_sys_metadata_field(field_id, &tmp);
  	if (ret)
  		return ret;
 
-	*st_member = tmp;
+	memcpy(st_member, &tmp, bytes);
 
  	return 0;
  }
@@ -295,11 +307,30 @@ static int read_sys_metadata_field16(u64 field_id,
  struct field_mapping {
  	u64 field_id;
  	int offset;
+	int size;
  };
 
Quoted 11 lines

arch/x86/virt/vmx/tdx/tdx.c static int read_sys_metadata_field16(u64 field_id, · line 318

Quoted 7 lines
arch/x86/virt/vmx/tdx/tdx.c +4 -1
  #define TD_SYSINFO_MAP(_field_id, _struct, _member)	\
  	{ .field_id = MD_FIELD_ID_##_field_id,		\
-	  .offset   = offsetof(_struct, _member) }
+	  .offset   = offsetof(_struct, _member),	\
+	  .size     = sizeof(typeof(((_struct *)0)->_member)) }
+
+static int read_sys_metadata(struct field_mapping *fields, int nr_fields,

The first parameter should be "const struct field_mapping *fields".

Indeed. :-)

#13 lore raw

PATCH v19 5/130 x86/virt/tdx: Export global metadata read infrastructure

In reply to [email protected]

From: Kai Huang <[email protected]>

KVM will need to read a bunch of non-TDMR related metadata to create and run TDX guests. Export the metadata read infrastructure for KVM to use.

Specifically, export two helpers:

1) The helper which reads multiple metadata fields to a buffer of a structure based on the "field ID -> structure member" mapping table.

2) The low level helper which just reads a given field ID.

The two helpers cover cases when the user wants to cache a bunch of metadata fields to a certain structure and when the user just wants to query a specific metadata field on demand. They are enough for KVM to use (and also should be enough for other potential users).

---
 arch/x86/include/asm/tdx.h  | 22 ++++++++++++++++++++++
 arch/x86/virt/vmx/tdx/tdx.c | 25 ++++++++-----------------
 2 files changed, 30 insertions(+), 17 deletions(-)
arch/x86/include/asm/tdx.h +22 -0
index eba178996d84..709b9483f9e4 100644
@@ -116,6 +116,28 @@ static inline u64 sc_retry(sc_func_t func, u64 fn,
 int tdx_cpu_enable(void);
 int tdx_enable(void);
 const char *tdx_dump_mce_info(struct mce *m);
+
+struct tdx_metadata_field_mapping {
+	u64 field_id;
+	int offset;
+	int size;
+};
+
+#define TD_SYSINFO_MAP(_field_id, _struct, _member)	\
+	{ .field_id = MD_FIELD_ID_##_field_id,		\
+	  .offset   = offsetof(_struct, _member),	\
+	  .size     = sizeof(typeof(((_struct *)0)->_member)) }
+
+/*
+ * Read multiple global metadata fields to a buffer of a structure
+ * based on the "field ID -> structure member" mapping table.
+ */
+int tdx_sys_metadata_read(const struct tdx_metadata_field_mapping *fields,
+			  int nr_fields, void *stbuf);
+
+/* Read a single global metadata field */
+int tdx_sys_metadata_field_read(u64 field_id, u64 *data);
+
 #else
 static inline void tdx_init(void) { }
 static inline int tdx_cpu_enable(void) { return -ENODEV; }
arch/x86/virt/vmx/tdx/tdx.c +8 -17
index a19adc898df6..dc21310776ab 100644
@@ -251,7 +251,7 @@ static int build_tdx_memlist(struct list_head *tmb_list)
 	return ret;
 }
 
-static int read_sys_metadata_field(u64 field_id, u64 *data)
+int tdx_sys_metadata_field_read(u64 field_id, u64 *data)
 {
 	struct tdx_module_args args = {};
 	int ret;
@@ -270,6 +270,7 @@ static int read_sys_metadata_field(u64 field_id, u64 *data)
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(tdx_sys_metadata_field_read);
 
 /* Return the metadata field element size in bytes */
 static int get_metadata_field_bytes(u64 field_id)
@@ -295,7 +296,7 @@ static int stbuf_read_sys_metadata_field(u64 field_id,
 	if (WARN_ON_ONCE(get_metadata_field_bytes(field_id) != bytes))
 		return -EINVAL;
 
-	ret = read_sys_metadata_field(field_id, &tmp);
+	ret = tdx_sys_metadata_field_read(field_id, &tmp);
 	if (ret)
 		return ret;
 
@@ -304,19 +305,8 @@ static int stbuf_read_sys_metadata_field(u64 field_id,
 	return 0;
 }
 
-struct field_mapping {
-	u64 field_id;
-	int offset;
-	int size;
-};
-
-#define TD_SYSINFO_MAP(_field_id, _struct, _member)	\
-	{ .field_id = MD_FIELD_ID_##_field_id,		\
-	  .offset   = offsetof(_struct, _member),	\
-	  .size     = sizeof(typeof(((_struct *)0)->_member)) }
-
-static int read_sys_metadata(struct field_mapping *fields, int nr_fields,
-			     void *stbuf)
+int tdx_sys_metadata_read(const struct tdx_metadata_field_mapping *fields,
+			  int nr_fields, void *stbuf)
 {
 	int i, ret;
 
@@ -331,6 +321,7 @@ static int read_sys_metadata(struct field_mapping *fields, int nr_fields,
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(tdx_sys_metadata_read);
 
 #define TD_SYSINFO_MAP_TDMR_INFO(_field_id, _member)	\
 	TD_SYSINFO_MAP(_field_id, struct tdx_tdmr_sysinfo, _member)
@@ -338,7 +329,7 @@ static int read_sys_metadata(struct field_mapping *fields, int nr_fields,
 static int get_tdx_tdmr_sysinfo(struct tdx_tdmr_sysinfo *tdmr_sysinfo)
 {
 	/* Map TD_SYSINFO fields into 'struct tdx_tdmr_sysinfo': */
-	const struct field_mapping fields[] = {
+	const struct tdx_metadata_field_mapping fields[] = {
 		TD_SYSINFO_MAP_TDMR_INFO(MAX_TDMRS,		max_tdmrs),
 		TD_SYSINFO_MAP_TDMR_INFO(MAX_RESERVED_PER_TDMR, max_reserved_per_tdmr),
 		TD_SYSINFO_MAP_TDMR_INFO(PAMT_4K_ENTRY_SIZE,    pamt_entry_size[TDX_PS_4K]),
@@ -347,7 +338,7 @@ static int get_tdx_tdmr_sysinfo(struct tdx_tdmr_sysinfo *tdmr_sysinfo)
 	};
 
 	/* Populate 'tdmr_sysinfo' fields using the mapping structure above: */
-	return read_sys_metadata(fields, ARRAY_SIZE(fields), tdmr_sysinfo);
+	return tdx_sys_metadata_read(fields, ARRAY_SIZE(fields), tdmr_sysinfo);
 }
 
 /* Calculate the actual TDMR size */
Signature · 1 line
2.25.1
#14 lore raw

Re: [PATCH v19 005/130] x86/virt/tdx: Export global metadata read infrastructure

Quoted 111 lines · On 26.02.2024 00:25, [email protected] wrote:

From: Kai Huang <[email protected]>

KVM will need to read a bunch of non-TDMR related metadata to create and run TDX guests. Export the metadata read infrastructure for KVM to use.

Specifically, export two helpers:

1) The helper which reads multiple metadata fields to a buffer of a structure based on the "field ID -> structure member" mapping table.

2) The low level helper which just reads a given field ID.

The two helpers cover cases when the user wants to cache a bunch of metadata fields to a certain structure and when the user just wants to query a specific metadata field on demand. They are enough for KVM to use (and also should be enough for other potential users).

---
arch/x86/include/asm/tdx.h | 22 ++++++++++++++++++++++ arch/x86/virt/vmx/tdx/tdx.c | 25 ++++++++-----------------

2 files changed, 30 insertions(+), 17 deletions(-)
arch/x86/include/asm/tdx.h +22 -0
index eba178996d84..709b9483f9e4 100644
@@ -116,6 +116,28 @@ static inline u64 sc_retry(sc_func_t func, u64 fn,
 int tdx_cpu_enable(void);
 int tdx_enable(void);
 const char *tdx_dump_mce_info(struct mce *m);
+
+struct tdx_metadata_field_mapping {
+	u64 field_id;
+	int offset;
+	int size;
+};
+
+#define TD_SYSINFO_MAP(_field_id, _struct, _member)	\
+	{ .field_id = MD_FIELD_ID_##_field_id,		\
+	  .offset   = offsetof(_struct, _member),	\
+	  .size     = sizeof(typeof(((_struct *)0)->_member)) }
+
+/*
+ * Read multiple global metadata fields to a buffer of a structure
+ * based on the "field ID -> structure member" mapping table.
+ */
+int tdx_sys_metadata_read(const struct tdx_metadata_field_mapping *fields,
+			  int nr_fields, void *stbuf);
+
+/* Read a single global metadata field */
+int tdx_sys_metadata_field_read(u64 field_id, u64 *data);
+
 #else
 static inline void tdx_init(void) { }
 static inline int tdx_cpu_enable(void) { return -ENODEV; }
arch/x86/virt/vmx/tdx/tdx.c +5 -15
index a19adc898df6..dc21310776ab 100644
@@ -251,7 +251,7 @@ static int build_tdx_memlist(struct list_head *tmb_list)
 	return ret;
 }
 
-static int read_sys_metadata_field(u64 field_id, u64 *data)
+int tdx_sys_metadata_field_read(u64 field_id, u64 *data)
 {
 	struct tdx_module_args args = {};
 	int ret;
@@ -270,6 +270,7 @@ static int read_sys_metadata_field(u64 field_id, u64 *data)
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(tdx_sys_metadata_field_read);
 
 /* Return the metadata field element size in bytes */
 static int get_metadata_field_bytes(u64 field_id)
@@ -295,7 +296,7 @@ static int stbuf_read_sys_metadata_field(u64 field_id,
 	if (WARN_ON_ONCE(get_metadata_field_bytes(field_id) != bytes))
 		return -EINVAL;
 
-	ret = read_sys_metadata_field(field_id, &tmp);
+	ret = tdx_sys_metadata_field_read(field_id, &tmp);
 	if (ret)
 		return ret;
 
@@ -304,19 +305,8 @@ static int stbuf_read_sys_metadata_field(u64 field_id,
 	return 0;
 }
 
-struct field_mapping {
-	u64 field_id;
-	int offset;
-	int size;
-};
-
-#define TD_SYSINFO_MAP(_field_id, _struct, _member)	\
-	{ .field_id = MD_FIELD_ID_##_field_id,		\
-	  .offset   = offsetof(_struct, _member),	\
-	  .size     = sizeof(typeof(((_struct *)0)->_member)) }
-
-static int read_sys_metadata(struct field_mapping *fields, int nr_fields,
-			     void *stbuf)
+int tdx_sys_metadata_read(const struct tdx_metadata_field_mapping *fields,
+			  int nr_fields, void *stbuf)
 {
 	int i, ret;
 

arch/x86/virt/vmx/tdx/tdx.c static int read_sys_metadata(struct field_mapping *fields, int nr_fields, · line 324

Quoted 4 lines
arch/x86/virt/vmx/tdx/tdx.c +1 -0
@@ -331,6 +321,7 @@ static int read_sys_metadata(struct field_mapping *fields, int nr_fields,
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(tdx_sys_metadata_read);

Hi Kai,

The two helpers can potentially be used by TD guests, as you mentioned. It's a good idea to declare it in the header asm/tdx.h.

However, the function cannot be compiled if its definition remains in the vmx/tdx/tdx.c file while disabling the CONFIG_TDX_HOST.

It would be better to move the definition to a shared location, allowing the host and guest to share the same code.

Thanks
--Sun, Yi

#15 lore raw

Re: [PATCH v19 005/130] x86/virt/tdx: Export global metadata read infrastructure

Quoted 8 lines

However, the function cannot be compiled if its definition remains in the vmx/tdx/tdx.c file while disabling the CONFIG_TDX_HOST.

It would be better to move the definition to a shared location, allowing the host and guest to share the same code.

No not in this series. Such change needs to be in your series.

Thanks,
-Kai

Reviewed-by on v19 5/130
#16 lore raw

Re: [PATCH v19 005/130] x86/virt/tdx: Export global metadata read infrastructure

In reply to [email protected]
Quoted 12 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Kai Huang <[email protected]>

KVM will need to read a bunch of non-TDMR related metadata to create and run TDX guests. Export the metadata read infrastructure for KVM to use.

Specifically, export two helpers:

1) The helper which reads multiple metadata fields to a buffer of a

    structure based on the "field ID -> structure member" mapping table.

2) The low level helper which just reads a given field ID.

Quoted 8 lines

The two helpers cover cases when the user wants to cache a bunch of metadata fields to a certain structure and when the user just wants to query a specific metadata field on demand. They are enough for KVM to use (and also should be enough for other potential users).

Quoted 116 lines
---
  arch/x86/include/asm/tdx.h  | 22 ++++++++++++++++++++++
  arch/x86/virt/vmx/tdx/tdx.c | 25 ++++++++-----------------
  2 files changed, 30 insertions(+), 17 deletions(-)
arch/x86/include/asm/tdx.h +22 -0
index eba178996d84..709b9483f9e4 100644
@@ -116,6 +116,28 @@ static inline u64 sc_retry(sc_func_t func, u64 fn,
  int tdx_cpu_enable(void);
  int tdx_enable(void);
  const char *tdx_dump_mce_info(struct mce *m);
+
+struct tdx_metadata_field_mapping {
+	u64 field_id;
+	int offset;
+	int size;
+};
+
+#define TD_SYSINFO_MAP(_field_id, _struct, _member)	\
+	{ .field_id = MD_FIELD_ID_##_field_id,		\
+	  .offset   = offsetof(_struct, _member),	\
+	  .size     = sizeof(typeof(((_struct *)0)->_member)) }
+
+/*
+ * Read multiple global metadata fields to a buffer of a structure
+ * based on the "field ID -> structure member" mapping table.
+ */
+int tdx_sys_metadata_read(const struct tdx_metadata_field_mapping *fields,
+			  int nr_fields, void *stbuf);
+
+/* Read a single global metadata field */
+int tdx_sys_metadata_field_read(u64 field_id, u64 *data);
+
  #else
  static inline void tdx_init(void) { }
  static inline int tdx_cpu_enable(void) { return -ENODEV; }
arch/x86/virt/vmx/tdx/tdx.c +8 -17
index a19adc898df6..dc21310776ab 100644
@@ -251,7 +251,7 @@ static int build_tdx_memlist(struct list_head *tmb_list)
  	return ret;
  }
 
-static int read_sys_metadata_field(u64 field_id, u64 *data)
+int tdx_sys_metadata_field_read(u64 field_id, u64 *data)
  {
  	struct tdx_module_args args = {};
  	int ret;
@@ -270,6 +270,7 @@ static int read_sys_metadata_field(u64 field_id, u64 *data)
 
  	return 0;
  }
+EXPORT_SYMBOL_GPL(tdx_sys_metadata_field_read);
 
  /* Return the metadata field element size in bytes */
  static int get_metadata_field_bytes(u64 field_id)
@@ -295,7 +296,7 @@ static int stbuf_read_sys_metadata_field(u64 field_id,
  	if (WARN_ON_ONCE(get_metadata_field_bytes(field_id) != bytes))
  		return -EINVAL;
 
-	ret = read_sys_metadata_field(field_id, &tmp);
+	ret = tdx_sys_metadata_field_read(field_id, &tmp);
  	if (ret)
  		return ret;
 
@@ -304,19 +305,8 @@ static int stbuf_read_sys_metadata_field(u64 field_id,
  	return 0;
  }
 
-struct field_mapping {
-	u64 field_id;
-	int offset;
-	int size;
-};
-
-#define TD_SYSINFO_MAP(_field_id, _struct, _member)	\
-	{ .field_id = MD_FIELD_ID_##_field_id,		\
-	  .offset   = offsetof(_struct, _member),	\
-	  .size     = sizeof(typeof(((_struct *)0)->_member)) }
-
-static int read_sys_metadata(struct field_mapping *fields, int nr_fields,
-			     void *stbuf)
+int tdx_sys_metadata_read(const struct tdx_metadata_field_mapping *fields,
+			  int nr_fields, void *stbuf)
  {
  	int i, ret;
 
@@ -331,6 +321,7 @@ static int read_sys_metadata(struct field_mapping *fields, int nr_fields,
 
  	return 0;
  }
+EXPORT_SYMBOL_GPL(tdx_sys_metadata_read);
 
  #define TD_SYSINFO_MAP_TDMR_INFO(_field_id, _member)	\
  	TD_SYSINFO_MAP(_field_id, struct tdx_tdmr_sysinfo, _member)
@@ -338,7 +329,7 @@ static int read_sys_metadata(struct field_mapping *fields, int nr_fields,
  static int get_tdx_tdmr_sysinfo(struct tdx_tdmr_sysinfo *tdmr_sysinfo)
  {
  	/* Map TD_SYSINFO fields into 'struct tdx_tdmr_sysinfo': */
-	const struct field_mapping fields[] = {
+	const struct tdx_metadata_field_mapping fields[] = {
  		TD_SYSINFO_MAP_TDMR_INFO(MAX_TDMRS,		max_tdmrs),
  		TD_SYSINFO_MAP_TDMR_INFO(MAX_RESERVED_PER_TDMR, max_reserved_per_tdmr),
  		TD_SYSINFO_MAP_TDMR_INFO(PAMT_4K_ENTRY_SIZE,    pamt_entry_size[TDX_PS_4K]),
@@ -347,7 +338,7 @@ static int get_tdx_tdmr_sysinfo(struct tdx_tdmr_sysinfo *tdmr_sysinfo)
  	};
 
  	/* Populate 'tdmr_sysinfo' fields using the mapping structure above: */
-	return read_sys_metadata(fields, ARRAY_SIZE(fields), tdmr_sysinfo);
+	return tdx_sys_metadata_read(fields, ARRAY_SIZE(fields), tdmr_sysinfo);
  }
 
  /* Calculate the actual TDMR size */
#17 lore raw

PATCH v19 6/130 x86/virt/tdx: Export TDX KeyID information

In reply to [email protected]

From: Kai Huang <[email protected]>

Each TDX guest must be protected by its own unique TDX KeyID. KVM will need to tell the TDX module the unique KeyID for a TDX guest when KVM creates it.

Export the TDX KeyID range that can be used by TDX guests for KVM to use. KVM can then manage these KeyIDs and assign one for each TDX guest when it is created.

Each TDX guest has a root control structure called "Trust Domain Root" (TDR). Unlike the rest of the TDX guest, the TDR is protected by the TDX global KeyID. When tearing down the TDR, KVM will need to pass the TDX global KeyID explicitly to the TDX module to flush cache associated to the TDR.

Also export the TDX global KeyID for KVM to tear down the TDR.

---
 arch/x86/include/asm/tdx.h  |  5 +++++
 arch/x86/virt/vmx/tdx/tdx.c | 11 ++++++++---
 2 files changed, 13 insertions(+), 3 deletions(-)
arch/x86/include/asm/tdx.h +5 -0
index 709b9483f9e4..16be3a1e4916 100644
@@ -88,6 +88,11 @@ static inline long tdx_kvm_hypercall(unsigned int nr, unsigned long p1,
 #endif /* CONFIG_INTEL_TDX_GUEST && CONFIG_KVM_GUEST */
 
 #ifdef CONFIG_INTEL_TDX_HOST
+
+extern u32 tdx_global_keyid;
+extern u32 tdx_guest_keyid_start;
+extern u32 tdx_nr_guest_keyids;
+
 u64 __seamcall(u64 fn, struct tdx_module_args *args);
 u64 __seamcall_ret(u64 fn, struct tdx_module_args *args);
 u64 __seamcall_saved_ret(u64 fn, struct tdx_module_args *args);
arch/x86/virt/vmx/tdx/tdx.c +8 -3
index dc21310776ab..d2b8f079a637 100644
@@ -39,9 +39,14 @@
 #include <asm/mce.h>
 #include "tdx.h"
 
-static u32 tdx_global_keyid __ro_after_init;
-static u32 tdx_guest_keyid_start __ro_after_init;
-static u32 tdx_nr_guest_keyids __ro_after_init;
+u32 tdx_global_keyid __ro_after_init;
+EXPORT_SYMBOL_GPL(tdx_global_keyid);
+
+u32 tdx_guest_keyid_start __ro_after_init;
+EXPORT_SYMBOL_GPL(tdx_guest_keyid_start);
+
+u32 tdx_nr_guest_keyids __ro_after_init;
+EXPORT_SYMBOL_GPL(tdx_nr_guest_keyids);
 
 static DEFINE_PER_CPU(bool, tdx_lp_initialized);
 
Signature · 1 line
2.25.1
#18 lore raw

PATCH v19 7/130 x86/virt/tdx: Export SEAMCALL functions

In reply to [email protected]

From: Kai Huang <[email protected]>

KVM will need to make SEAMCALLs to create and run TDX guests. Export SEAMCALL functions for KVM to use.

Also add declaration of SEAMCALL functions to <asm/asm-prototypes.h> to support CONFIG_MODVERSIONS=y.

---
 arch/x86/include/asm/asm-prototypes.h | 1 +
 arch/x86/virt/vmx/tdx/seamcall.S      | 4 ++++
 2 files changed, 5 insertions(+)
arch/x86/include/asm/asm-prototypes.h +1 -0
index b1a98fa38828..0ec572ad75f1 100644
@@ -13,6 +13,7 @@
 #include <asm/preempt.h>
 #include <asm/asm.h>
 #include <asm/gsseg.h>
+#include <asm/tdx.h>
 
 #ifndef CONFIG_X86_CMPXCHG64
 extern void cmpxchg8b_emu(void);
arch/x86/virt/vmx/tdx/seamcall.S +4 -0
index 5b1f2286aea9..e32cf82ed47e 100644
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 #include <linux/linkage.h>
+#include <linux/export.h>
 #include <asm/frame.h>
 
 #include "tdxcall.S"
@@ -21,6 +22,7 @@
 SYM_FUNC_START(__seamcall)
 	TDX_MODULE_CALL host=1
 SYM_FUNC_END(__seamcall)
+EXPORT_SYMBOL_GPL(__seamcall);
 
 /*
  * __seamcall_ret() - Host-side interface functions to SEAM software
@@ -40,6 +42,7 @@ SYM_FUNC_END(__seamcall)
 SYM_FUNC_START(__seamcall_ret)
 	TDX_MODULE_CALL host=1 ret=1
 SYM_FUNC_END(__seamcall_ret)
+EXPORT_SYMBOL_GPL(__seamcall_ret);
 
 /*
  * __seamcall_saved_ret() - Host-side interface functions to SEAM software
@@ -59,3 +62,4 @@ SYM_FUNC_END(__seamcall_ret)
 SYM_FUNC_START(__seamcall_saved_ret)
 	TDX_MODULE_CALL host=1 ret=1 saved=1
 SYM_FUNC_END(__seamcall_saved_ret)
+EXPORT_SYMBOL_GPL(__seamcall_saved_ret);
Signature · 1 line
2.25.1
#19 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 5 lines · On 26/02/2024 9:25 pm, [email protected] wrote:

From: Kai Huang <[email protected]>

KVM will need to make SEAMCALLs to create and run TDX guests. Export SEAMCALL functions for KVM to use.

Could you also list the reason that we want to expose __seamcall() directly, rather than wanting to put some higher level wrappers in the TDX host code, and export them?

For example, we can give a summary of the SEAMCALLs (e.g., how many in total, and roughly introduce them based on categories) that will be used by KVM, and clarify the reasons why we want to just export __seamcall().

E.g., we can say something like this:

TD;LR:

KVM roughly will need to use dozens of SEAMCALLs, and all these are logically related to creating and running TDX guests. It makes more sense to just export __seamcall() and let KVM maintain these VM-related wrappers rather than having the TDX host code to provide wrappers for each SEAMCALL or higher-level abstraction.

Long version:

You give a detailed explanation of SEAMCALLs that will be used by KVM, and clarify logically it's better to manage these code in KVM.

#20 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Dave, care to weigh in here?

Context (whether to export the generic __seamcall for KVM's use):
https://lore.kernel.org/lkml/[email protected]/

Quoted 6 lines · Kai

KVM roughly will need to use dozens of SEAMCALLs, and all these are logically related to creating and running TDX guests.  It makes more sense to just export __seamcall() and let KVM maintain these VM- related
wrappers rather than having the TDX host code to provide wrappers for each SEAMCALL or higher-level abstraction.

The helpers being discussed are these:
https://lore.kernel.org/lkml/7cfd33d896fce7b49bcf4b7179d0ded22c06b8c2.1708933498.git.isaku.yamahata@intel.com/

I guess there are three options:
1. Export the low level seamcall function
2. Export a bunch of higher level helper functions
3. Duplicate __seamcall asm in KVM

Letting modules make unrestricted seamcalls is not ideal. Preventing the compiler from inlining the small logic in the static inline helpers is not ideal. Duplicating code is not ideal. Hmm.

I want to say 2 sounds the least worst of the three. But I'm not sure. I'm not sure if x86 folks would like to police new seamcalls, or be bothered by it, either.

#21 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 5 lines · Rick P

I guess there are three options:
1. Export the low level seamcall function
2. Export a bunch of higher level helper functions
3. Duplicate __seamcall asm in KVM

Quoted 7 lines

Letting modules make unrestricted seamcalls is not ideal. Preventing the compiler from inlining the small logic in the static inline helpers is not ideal. Duplicating code is not ideal. Hmm.

I want to say 2 sounds the least worst of the three. But I'm not sure. I'm not sure if x86 folks would like to police new seamcalls, or be bothered by it, either.

#3 is the only objectively awful one. :)

In the end, we actually _want_ to have conversations about these things.
There are going to be considerations about what functionality should be
in KVM or the core kernel. We don't want KVM doing any calls that could affect global TDX module state, for instance.

But I'd also defer to the KVM maintainers on this. They're the ones that have to play the symbol exporting game a lot more than I ever do. If they cringe at the idea of adding 20 (or whatever) exports, then that's a lot more important than the possibility of some other silly module abusing the generic exported __seamcall.

#22 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 14 lines · Dave Hansen
Quoted 5 lines · Rick P

I guess there are three options:
1. Export the low level seamcall function
2. Export a bunch of higher level helper functions
3. Duplicate __seamcall asm in KVM

Quoted 7 lines

Letting modules make unrestricted seamcalls is not ideal. Preventing the compiler from inlining the small logic in the static inline helpers is not ideal. Duplicating code is not ideal. Hmm.

I want to say 2 sounds the least worst of the three. But I'm not sure. I'm not sure if x86 folks would like to police new seamcalls, or be bothered by it, either.

Quoted 6 lines

#3 is the only objectively awful one. :)

In the end, we actually _want_ to have conversations about these things.
There are going to be considerations about what functionality should be
in KVM or the core kernel. We don't want KVM doing any calls that could affect global TDX module state, for instance.

Heh, Like this one?

        static inline u64 tdh_sys_lp_shutdown(void)
        {
        	struct tdx_module_args in = {
        	};
        
        	return tdx_seamcall(TDH_SYS_LP_SHUTDOWN, &in, NULL);
        }

Which isn't actually used...

Quoted 5 lines

But I'd also defer to the KVM maintainers on this. They're the ones that have to play the symbol exporting game a lot more than I ever do. If they cringe at the idea of adding 20 (or whatever) exports, then that's a lot more important than the possibility of some other silly module abusing the generic exported __seamcall.

I don't care much about exports. What I do care about is sane code, and while the current code _looks_ pretty, it's actually quite insane.

I get why y'all put SEAMCALL in assembly subroutines; the macro shenanigans I originally wrote years ago were their own brand of crazy, and dealing with GPRs that can't be asm() constraints often results in brittle code.

But the tdx_module_args structure approach generates truly atrocious code. Yes, SEAMCALL is inherently slow, but that doesn't mean that we shouldn't at least try to generate efficient code. And it's not just efficiency that is lost, the generated code ends up being much harder to read than it ought to be.

E.g. the seemingly simple

        static inline u64 tdh_mem_page_remove(hpa_t tdr, gpa_t gpa, int level,
        				      struct tdx_module_args *out)
        {
        	struct tdx_module_args in = {
        		.rcx = gpa | level,
        		.rdx = tdr,
        	};

        	return tdx_seamcall_sept(TDH_MEM_PAGE_REMOVE, &in, out);
        }

generates the below monstrosity with gcc-13. And that's just one SEAMCALL wrapper, *every* single one generates the same mess. clang-16 is kinda sorta a little better, as it at least inlines the helpers that have single callers.

So my feedback is to not worry about the exports, and instead focus on figuring out a way to make the generated code less bloated and easier to read/debug.

Dump of assembler code for function tdh_mem_page_remove:
0x0000000000032b20 <+0>: push %r15
0x0000000000032b22 <+2>: xor %eax,%eax
0x0000000000032b24 <+4>: movabs $0x8000ff0000000006,%r15

   0x0000000000032b2e <+14>:	push   %r14
   0x0000000000032b30 <+16>:	mov    %rcx,%r14
   0x0000000000032b33 <+19>:	mov    $0xb,%ecx
   0x0000000000032b38 <+24>:	push   %r13
   0x0000000000032b3a <+26>:	movslq %edx,%r13
   0x0000000000032b3d <+29>:	push   %r12
   0x0000000000032b3f <+31>:	or     %rsi,%r13
   0x0000000000032b42 <+34>:	mov    $0x11,%r12d
   0x0000000000032b48 <+40>:	push   %rbp
   0x0000000000032b49 <+41>:	movabs $0x8000020300000000,%rbp
   0x0000000000032b53 <+51>:	push   %rbx
   0x0000000000032b54 <+52>:	sub    $0x70,%rsp
   0x0000000000032b58 <+56>:	mov    %rdi,(%rsp)
   0x0000000000032b5c <+60>:	lea    0x18(%rsp),%rdi
   0x0000000000032b61 <+65>:	rep stos %rax,%es:(%rdi)
   0x0000000000032b64 <+68>:	mov    (%rsp),%rax
   0x0000000000032b68 <+72>:	mov    %r13,(%r14)
   0x0000000000032b6b <+75>:	mov    $0xa,%ebx
   0x0000000000032b70 <+80>:	mov    %rax,0x8(%r14)
   0x0000000000032b74 <+84>:	mov    0x18(%rsp),%rax
   0x0000000000032b79 <+89>:	mov    %rax,0x10(%r14)
   0x0000000000032b7d <+93>:	mov    0x20(%rsp),%rax
   0x0000000000032b82 <+98>:	mov    %rax,0x18(%r14)
   0x0000000000032b86 <+102>:	mov    0x28(%rsp),%rax
   0x0000000000032b8b <+107>:	mov    %rax,0x20(%r14)
   0x0000000000032b8f <+111>:	mov    0x30(%rsp),%rax
   0x0000000000032b94 <+116>:	mov    %rax,0x28(%r14)
   0x0000000000032b98 <+120>:	mov    0x38(%rsp),%rax
   0x0000000000032b9d <+125>:	mov    %rax,0x30(%r14)
   0x0000000000032ba1 <+129>:	mov    0x40(%rsp),%rax
   0x0000000000032ba6 <+134>:	mov    %rax,0x38(%r14)
   0x0000000000032baa <+138>:	mov    0x48(%rsp),%rax
   0x0000000000032baf <+143>:	mov    %rax,0x40(%r14)
   0x0000000000032bb3 <+147>:	mov    0x50(%rsp),%rax
   0x0000000000032bb8 <+152>:	mov    %rax,0x48(%r14)
   0x0000000000032bbc <+156>:	mov    0x58(%rsp),%rax
   0x0000000000032bc1 <+161>:	mov    %rax,0x50(%r14)
   0x0000000000032bc5 <+165>:	mov    0x60(%rsp),%rax
   0x0000000000032bca <+170>:	mov    %rax,0x58(%r14)
   0x0000000000032bce <+174>:	mov    0x68(%rsp),%rax
   0x0000000000032bd3 <+179>:	mov    %rax,0x60(%r14)
   0x0000000000032bd7 <+183>:	mov    %r14,%rsi
   0x0000000000032bda <+186>:	mov    $0x1d,%edi
   0x0000000000032bdf <+191>:	call   0x32be4 <tdh_mem_page_remove+196>
   0x0000000000032be4 <+196>:	cmp    %rbp,%rax
   0x0000000000032be7 <+199>:	jne    0x32bfd <tdh_mem_page_remove+221>
   0x0000000000032be9 <+201>:	sub    $0x1,%ebx
   0x0000000000032bec <+204>:	jne    0x32bd7 <tdh_mem_page_remove+183>
   0x0000000000032bee <+206>:	add    $0x70,%rsp
   0x0000000000032bf2 <+210>:	pop    %rbx
   0x0000000000032bf3 <+211>:	pop    %rbp
   0x0000000000032bf4 <+212>:	pop    %r12
   0x0000000000032bf6 <+214>:	pop    %r13
   0x0000000000032bf8 <+216>:	pop    %r14
   0x0000000000032bfa <+218>:	pop    %r15
   0x0000000000032bfc <+220>:	ret
   0x0000000000032bfd <+221>:	cmp    %r15,%rax
   0x0000000000032c00 <+224>:	je     0x32c2a <tdh_mem_page_remove+266>
   0x0000000000032c02 <+226>:	movabs $0x8000020000000092,%rdx
   0x0000000000032c0c <+236>:	cmp    %rdx,%rax
   0x0000000000032c0f <+239>:	jne    0x32bee <tdh_mem_page_remove+206>
   0x0000000000032c11 <+241>:	sub    $0x1,%r12d
   0x0000000000032c15 <+245>:	jne    0x32b64 <tdh_mem_page_remove+68>
   0x0000000000032c1b <+251>:	add    $0x70,%rsp
   0x0000000000032c1f <+255>:	pop    %rbx
   0x0000000000032c20 <+256>:	pop    %rbp
   0x0000000000032c21 <+257>:	pop    %r12
   0x0000000000032c23 <+259>:	pop    %r13
   0x0000000000032c25 <+261>:	pop    %r14
   0x0000000000032c27 <+263>:	pop    %r15
   0x0000000000032c29 <+265>:	ret
   0x0000000000032c2a <+266>:	call   0x32c2f <tdh_mem_page_remove+271>
   0x0000000000032c2f <+271>:	xor    %eax,%eax
   0x0000000000032c31 <+273>:	add    $0x70,%rsp
   0x0000000000032c35 <+277>:	pop    %rbx
   0x0000000000032c36 <+278>:	pop    %rbp
   0x0000000000032c37 <+279>:	pop    %r12
   0x0000000000032c39 <+281>:	pop    %r13
   0x0000000000032c3b <+283>:	pop    %r14
   0x0000000000032c3d <+285>:	pop    %r15

0x0000000000032c3f <+287>: ret
End of assembler dump.

#23 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 2 lines · Sean Christopherson

So my feedback is to not worry about the exports, and instead focus on figuring out a way to make the generated code less bloated and easier to read/debug.

Oh, and please make it a collaborative, public effort. I don't want to hear crickets and then see v20 dropped with a completely new SEAMCALL scheme.

#24 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 10 lines · Sean Christopherson
Quoted 4 lines · Sean Christopherson

So my feedback is to not worry about the exports, and instead focus on figuring
out a way to make the generated code less bloated and easier to read/debug.

Oh, and please make it a collaborative, public effort.  I don't want to hear
crickets and then see v20 dropped with a completely new SEAMCALL scheme.

And here we we're worrying that people might eventually grow tired of us adding mails to v19 and we debate every detail in public. Will do.

#25 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 6 lines · Rick P Edgecombe
Quoted 5 lines · Sean Christopherson
Quoted 3 lines · Sean Christopherson

So my feedback is to not worry about the exports, and instead focus on figuring out a way to make the generated code less bloated and easier to read/debug.

Quoted 6 lines
Quoted 3 lines

Oh, and please make it a collaborative, public effort.  I don't want to hear crickets and then see v20 dropped with a completely new SEAMCALL scheme.

And here we we're worrying that people might eventually grow tired of us adding mails to v19 and we debate every detail in public. Will do.

As a general rule, I _strongly_ prefer all review to be done on-list, in public. Copy+pasting myself from another Intel series[*]

: Correct, what I object to is Intel _requiring_ a Reviewed-by before posting. :
: And while I'm certainly not going to refuse patches that have been reviewed : internally, I _strongly_ prefer reviews be on-list so that they are public and : recorded. Being able to go back and look at the history and evolution of patches : is valuable, and the discussion itself is often beneficial to non-participants, : e.g. people that are new-ish to KVM and/or aren't familiar with the feature being : enabled can often learn new things and avoid similar pitfalls of their own.

There are definitely situations where exceptions are warranted, e.g. if someone is a first-time poster and/or wants a sanity check to make sure their idea isn't completely crazy. But even then, the internal review should only be very cursory.

In addition to the history being valuable, doing reviews in public minimizes the probability of a developer being led astray, e.g. due to someone internally saying do XYZ, and then upstream reviewers telling them to do something entirely different.

As far as noise goes, look at it this way. Every time a new TDX series is posted, I get 130+ emails. Y'all can do a _lot_ of public review and discussion before you'll get anywhere near the point where it'd be noiser than spinning a new version of the series.

[*] https://lore.kernel.org/all/[email protected]

#26 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

In reply to Sean Christopherson
Quoted 11 lines · Sean Christopherson

Heh, Like this one?

        static inline u64 tdh_sys_lp_shutdown(void)
        {
                struct tdx_module_args in = {
                };

                return tdx_seamcall(TDH_SYS_LP_SHUTDOWN, &in, NULL); }

Which isn't actually used...

Looks like is was turned into a NOP in TDX 1.5. So will even forever be dead code. I see one other that is unused. Thanks for pointing it out.

Quoted 14 lines
Quoted 9 lines

But I'd also defer to the KVM maintainers on this.  They're the ones
that have to play the symbol exporting game a lot more than I ever do.
If they cringe at the idea of adding 20 (or whatever) exports, then that's a lot more important than the possibility of some other silly
module abusing the generic exported __seamcall.

I don't care much about exports.  What I do care about is sane code, and while
the current code _looks_ pretty, it's actually quite insane.

Quoted 5 lines

I get why y'all put SEAMCALL in assembly subroutines; the macro shenanigans I
originally wrote years ago were their own brand of crazy, and dealing with GPRs
that can't be asm() constraints often results in brittle code.

I guess it must be this, for the initiated:
https://lore.kernel.org/lkml/25f0d2c2f73c20309a1b578cc5fc15f4fd6b9a13.1605232743.git.isaku.yamahata@intel.com/

Quoted 10 lines

But the tdx_module_args structure approach generates truly atrocious code.  Yes,
SEAMCALL is inherently slow, but that doesn't mean that we shouldn't at least try
to generate efficient code.  And it's not just efficiency that is lost, the
generated code ends up being much harder to read than it ought to be.

[snip]

Quoted 6 lines

So my feedback is to not worry about the exports, and instead focus on figuring
out a way to make the generated code less bloated and easier to read/debug.

Thanks for the feedback both! It sounds like everyone is flexible on the exports. As for the generated code, oof.

Kai, I see the solution has gone through some iterations already. First the macro one linked above, then that was dropped pretty quick to something that loses the asm constraints:
https://lore.kernel.org/lkml/e777bbbe10b1ec2c37d85dcca2e175fe3bc565ec.1625186503.git.isaku.yamahata@intel.com/

Then next the struct grew here, and here:
https://lore.kernel.org/linux-mm/[email protected]/
https://lore.kernel.org/linux-mm/[email protected]/

Not sure I understand all of the constraints yet. Do you have any ideas?

#27 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 30 lines · Rick P
Quoted 11 lines · Sean Christopherson

Heh, Like this one?

        static inline u64 tdh_sys_lp_shutdown(void)
        {
                struct tdx_module_args in = {
                };

                return tdx_seamcall(TDH_SYS_LP_SHUTDOWN, &in, NULL); }

Which isn't actually used...

Looks like is was turned into a NOP in TDX 1.5. So will even forever be dead code. I see one other that is unused. Thanks for pointing it out.

Quoted 14 lines
Quoted 9 lines

But I'd also defer to the KVM maintainers on this.  They're the ones
that have to play the symbol exporting game a lot more than I ever do.
If they cringe at the idea of adding 20 (or whatever) exports, then that's a lot more important than the possibility of some other silly
module abusing the generic exported __seamcall.

I don't care much about exports.  What I do care about is sane code, and while
the current code _looks_ pretty, it's actually quite insane.

Quoted 8 lines
Quoted 5 lines

I get why y'all put SEAMCALL in assembly subroutines; the macro shenanigans I
originally wrote years ago were their own brand of crazy, and dealing with GPRs
that can't be asm() constraints often results in brittle code.

I guess it must be this, for the initiated:
https://lore.kernel.org/lkml/25f0d2c2f73c20309a1b578cc5fc15f4fd6b9a13.1605232743.git.isaku.yamahata@intel.com/

Hmm.. I lost memory of this. :-(

Quoted 19 lines
Quoted 11 lines

But the tdx_module_args structure approach generates truly atrocious code.  Yes,
SEAMCALL is inherently slow, but that doesn't mean that we shouldn't at least try
to generate efficient code.  And it's not just efficiency that is lost, the
generated code ends up being much harder to read than it ought to be.

[snip]

Quoted 6 lines

So my feedback is to not worry about the exports, and instead focus on figuring
out a way to make the generated code less bloated and easier to read/debug.

Quoted 7 lines

Thanks for the feedback both! It sounds like everyone is flexible on the exports. As for the generated code, oof.

Kai, I see the solution has gone through some iterations already. First the macro one linked above, then that was dropped pretty quick to something that loses the asm constraints:
https://lore.kernel.org/lkml/e777bbbe10b1ec2c37d85dcca2e175fe3bc565ec.1625186503.git.isaku.yamahata@intel.com/

Sorry I forgot for what reason we changed from the (bunch of) macros to this.

Quoted 7 lines

Then next the struct grew here, and here:
https://lore.kernel.org/linux-mm/[email protected]/
https://lore.kernel.org/linux-mm/[email protected]/

Not sure I understand all of the constraints yet. Do you have any ideas?

This was due to Peter's request to unify the TDCALL/SEAMCALL and TDVMCALL assembly, which was done by this series:

https://lore.kernel.org/lkml/[email protected]/

So when Peter requested this, the __seamcall() and __tdcall() (or __tdx_module_call() before the above series) were already sharing one assembly macro. The TDVMCALL were using a different macro, though. However the two assembly macros used similar structure and similar code, so we tried to unify them.

And another reason that we changed from ...

  u64 __seamcall(u64 fn, u64 rcx, u64 rdx, ..., struct tdx_module_args *out);

to ...

  u64 __seamcall(u64, struct tdx_module_args *args);

... was the former doesn't extend.

E.g., live migration related new SEAMCALLs use more registers as input. It is just insane to have so many individual regs as function argument.

And Peter wanted to use __seamcall() to cover TDH.VP.ENTER too, which historically had been implemented in KVM using its own assembly, because VP.ENTER basically uses all GPRs as input/output.

#28 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

In reply to Sean Christopherson
Quoted 11 lines · Sean Christopherson
        static inline u64 tdh_mem_page_remove(hpa_t tdr, gpa_t gpa, int level,
        				      struct tdx_module_args *out)
        {
        	struct tdx_module_args in = {
        		.rcx = gpa | level,
        		.rdx = tdr,
        	};

        	return tdx_seamcall_sept(TDH_MEM_PAGE_REMOVE, &in, out);
        }
Quoted 3 lines

generates the below monstrosity with gcc-13. And that's just one SEAMCALL wrapper, *every* single one generates the same mess. clang-16 is kinda sorta a little better, as it at least inlines the helpers that have single callers.

Yeah, that's really awful.

Is all the inlining making the compiler too ambitious? Why is this all inlined in the first place?

tdh_mem_page_remove() _should_ just be logically:

* initialize tdx_module_args. Move a few things into place on the stack and zero the rest.
* Put a pointer to tdx_module_args in a register
* Put TDH_MEM_PAGE_REMOVE immediate in a register
* Some register preservation, maybe
* call
* maybe some cleanup
* return

Those logical things are *NOT* easy to spot in the disassembly.

#29 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 12 lines · Dave Hansen
Quoted 11 lines · Sean Christopherson
        static inline u64 tdh_mem_page_remove(hpa_t tdr, gpa_t gpa, int level,
        				      struct tdx_module_args *out)
        {
        	struct tdx_module_args in = {
        		.rcx = gpa | level,
        		.rdx = tdr,
        	};

        	return tdx_seamcall_sept(TDH_MEM_PAGE_REMOVE, &in, out);
        }
Quoted 7 lines
Quoted 3 lines

generates the below monstrosity with gcc-13. And that's just one SEAMCALL wrapper, *every* single one generates the same mess. clang-16 is kinda sorta a little better, as it at least inlines the helpers that have single callers.

Yeah, that's really awful.

Is all the inlining making the compiler too ambitious?

No, whether or not the wrappers are inlined doesn't change anything. gcc actually doesn't inline any of these helpers. More below.

Quoted 1 line

Why is this all inlined in the first place?

Likely because no one looked at the generated code. The C code is super simple and looks like it should be inlined.

And the very original code was macro heavy, i.e. relied on inlining to allow the compiler to precisely set only the actual registers needed for the SEAMCALL.

Quoted 4 lines

tdh_mem_page_remove() _should_ just be logically:

* initialize tdx_module_args. Move a few things into place on the stack and zero the rest.

The "zero the rest" is what generates the fugly code. The underlying problem is that the SEAMCALL assembly functions unpack _all_ registers from tdx_module_args. As a result, tdx_module_args needs to be zeroed to avoid loading registers with unitialized stack data.

E.g. before I looked at the assembly code, my initial thought to clean things up by doing:

	struct tdx_module_args in;

	in.rcx = gpa | level;
	in.rdx = tdr;

but that would make one or more sanitizers (and maybe even the compiler itself) more than a bit unhappy.

The struct is 72 bytes, which adds up to a lot of wasted effort since the majority of SEAMCALLs only use a few of the 13 registers.

FWIW, the guest side of TDX is equally gross. E.g. to do kvm_hypercall1(), which without TDX is simply

0xffffffff810eb4a2 <+82>: 48 c7 c0 8c 9a 01 00 mov $0x19a8c,%rax

   0xffffffff810eb4a9 <+89>:	8b 1c 02           	mov    (%rdx,%rax,1),%ebx
   0xffffffff810eb4ac <+92>:	b8 0b 00 00 00     	mov    $0xb,%eax
   0xffffffff810eb4b1 <+97>:	0f 01 c1           	vmcall
   0xffffffff810eb4b4 <+100>:	5b                 	pop    %rbx
   0xffffffff810eb4b5 <+101>:	5d                 	pop    %rbp

the kernel blasts the unused params

0xffffffff810ffdc1 <+97>: bf 0b 00 00 00 mov $0xb,%edi

   0xffffffff810ffdc6 <+102>:	31 d2              	xor    %edx,%edx
   0xffffffff810ffdc8 <+104>:	31 c9              	xor    %ecx,%ecx
   0xffffffff810ffdca <+106>:	45 31 c0           	xor    %r8d,%r8d
   0xffffffff810ffdcd <+109>:	5b                 	pop    %rbx
   0xffffffff810ffdce <+110>:	41 5e              	pop    %r14
   0xffffffff810ffdd0 <+112>:	e9 bb 1b f0 ff     	jmp    0xffffffff81001990 <tdx_kvm_hypercall>

then loads and zeros a ton of memory (tdx_kvm_hypercall()):

   0xffffffff81001990 <+0>:	nopl   0x0(%rax,%rax,1)
   0xffffffff81001995 <+5>:	sub    $0x70,%rsp
   0xffffffff81001999 <+9>:	mov    %gs:0x28,%rax
   0xffffffff810019a2 <+18>:	mov    %rax,0x68(%rsp)
   0xffffffff810019a7 <+23>:	mov    %edi,%eax
   0xffffffff810019a9 <+25>:	movq   $0x0,0x18(%rsp)
   0xffffffff810019b2 <+34>:	movq   $0x0,0x10(%rsp)
   0xffffffff810019bb <+43>:	movq   $0x0,0x8(%rsp)
   0xffffffff810019c4 <+52>:	movq   $0x0,(%rsp)
   0xffffffff810019cc <+60>:	mov    %rax,0x20(%rsp)
   0xffffffff810019d1 <+65>:	mov    %rsi,0x28(%rsp)
   0xffffffff810019d6 <+70>:	mov    %rdx,0x30(%rsp)
   0xffffffff810019db <+75>:	mov    %rcx,0x38(%rsp)
   0xffffffff810019e0 <+80>:	mov    %r8,0x40(%rsp)
   0xffffffff810019e5 <+85>:	movq   $0x0,0x48(%rsp)
   0xffffffff810019ee <+94>:	movq   $0x0,0x50(%rsp)
   0xffffffff810019f7 <+103>:	movq   $0x0,0x58(%rsp)
   0xffffffff81001a00 <+112>:	movq   $0x0,0x60(%rsp)
   0xffffffff81001a09 <+121>:	mov    %rsp,%rdi
   0xffffffff81001a0c <+124>:	call   0xffffffff819f0a80 <__tdx_hypercall>
   0xffffffff81001a11 <+129>:	mov    %gs:0x28,%rcx
   0xffffffff81001a1a <+138>:	cmp    0x68(%rsp),%rcx
   0xffffffff81001a1f <+143>:	jne    0xffffffff81001a26 <tdx_kvm_hypercall+150>
   0xffffffff81001a21 <+145>:	add    $0x70,%rsp

0xffffffff81001a25 <+149>: ret

and then unpacks all of that memory back into registers, and reverses that last part on the way back, (__tdcall_saved_ret()):

   0xffffffff819f0b10 <+0>:	mov    %rdi,%rax
   0xffffffff819f0b13 <+3>:	mov    (%rsi),%rcx
   0xffffffff819f0b16 <+6>:	mov    0x8(%rsi),%rdx
   0xffffffff819f0b1a <+10>:	mov    0x10(%rsi),%r8
   0xffffffff819f0b1e <+14>:	mov    0x18(%rsi),%r9
   0xffffffff819f0b22 <+18>:	mov    0x20(%rsi),%r10
   0xffffffff819f0b26 <+22>:	mov    0x28(%rsi),%r11
   0xffffffff819f0b2a <+26>:	push   %rbx
   0xffffffff819f0b2b <+27>:	push   %r12
   0xffffffff819f0b2d <+29>:	push   %r13
   0xffffffff819f0b2f <+31>:	push   %r14
   0xffffffff819f0b31 <+33>:	push   %r15
   0xffffffff819f0b33 <+35>:	mov    0x30(%rsi),%r12
   0xffffffff819f0b37 <+39>:	mov    0x38(%rsi),%r13
   0xffffffff819f0b3b <+43>:	mov    0x40(%rsi),%r14
   0xffffffff819f0b3f <+47>:	mov    0x48(%rsi),%r15
   0xffffffff819f0b43 <+51>:	mov    0x50(%rsi),%rbx
   0xffffffff819f0b47 <+55>:	push   %rsi
   0xffffffff819f0b48 <+56>:	mov    0x58(%rsi),%rdi
   0xffffffff819f0b4c <+60>:	mov    0x60(%rsi),%rsi
   0xffffffff819f0b50 <+64>:	tdcall
   0xffffffff819f0b54 <+68>:	push   %rax
   0xffffffff819f0b55 <+69>:	mov    0x8(%rsp),%rax
   0xffffffff819f0b5a <+74>:	mov    %rsi,0x60(%rax)
   0xffffffff819f0b5e <+78>:	pop    %rax
   0xffffffff819f0b5f <+79>:	pop    %rsi
   0xffffffff819f0b60 <+80>:	mov    %r12,0x30(%rsi)
   0xffffffff819f0b64 <+84>:	mov    %r13,0x38(%rsi)
   0xffffffff819f0b68 <+88>:	mov    %r14,0x40(%rsi)
   0xffffffff819f0b6c <+92>:	mov    %r15,0x48(%rsi)
   0xffffffff819f0b70 <+96>:	mov    %rbx,0x50(%rsi)
   0xffffffff819f0b74 <+100>:	mov    %rdi,0x58(%rsi)
   0xffffffff819f0b78 <+104>:	mov    %rcx,(%rsi)
   0xffffffff819f0b7b <+107>:	mov    %rdx,0x8(%rsi)
   0xffffffff819f0b7f <+111>:	mov    %r8,0x10(%rsi)
   0xffffffff819f0b83 <+115>:	mov    %r9,0x18(%rsi)
   0xffffffff819f0b87 <+119>:	mov    %r10,0x20(%rsi)
   0xffffffff819f0b8b <+123>:	mov    %r11,0x28(%rsi)
   0xffffffff819f0b8f <+127>:	xor    %ecx,%ecx
   0xffffffff819f0b91 <+129>:	xor    %edx,%edx
   0xffffffff819f0b93 <+131>:	xor    %r8d,%r8d
   0xffffffff819f0b96 <+134>:	xor    %r9d,%r9d
   0xffffffff819f0b99 <+137>:	xor    %r10d,%r10d
   0xffffffff819f0b9c <+140>:	xor    %r11d,%r11d
   0xffffffff819f0b9f <+143>:	xor    %r12d,%r12d
   0xffffffff819f0ba2 <+146>:	xor    %r13d,%r13d
   0xffffffff819f0ba5 <+149>:	xor    %r14d,%r14d
   0xffffffff819f0ba8 <+152>:	xor    %r15d,%r15d
   0xffffffff819f0bab <+155>:	xor    %ebx,%ebx
   0xffffffff819f0bad <+157>:	xor    %edi,%edi
   0xffffffff819f0baf <+159>:	pop    %r15
   0xffffffff819f0bb1 <+161>:	pop    %r14
   0xffffffff819f0bb3 <+163>:	pop    %r13
   0xffffffff819f0bb5 <+165>:	pop    %r12
   0xffffffff819f0bb7 <+167>:	pop    %rbx

0xffffffff819f0bb8 <+168>: ret

It's honestly quite amusing, because y'all took one what I see as one of the big advantages of TDX over SEV (using registers instead of shared memory), and managed to effectively turn it into a disadvantage.

Again, I completely understand the maintenance and robustness benefits, but IMO the pendulum swung a bit too far in that direction.

#30 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 8 lines · Sean Christopherson
Quoted 4 lines

tdh_mem_page_remove() _should_ just be logically:

* initialize tdx_module_args. Move a few things into place on the stack and zero the rest.

The "zero the rest" is what generates the fugly code. The underlying problem is that the SEAMCALL assembly functions unpack _all_ registers from tdx_module_args. As a result, tdx_module_args needs to be zeroed to avoid loading registers with unitialized stack data.

It's the "zero the rest" and also the copy:

Review

Quoted 5 lines
(quoted diff) +5 -0
+	if (out) {
+		*out = *in;
+		ret = seamcall_ret(op, out);
+	} else
+		ret = seamcall(op, in);

Things get a wee bit nicer if you do an out-of-line mempcy() instead of the structure copy. But the really fun part is that 'out' is NULL and the compiler *SHOULD* know it. I'm not actually sure what trips it up.

In any case, I think it ends up generating code for both sides of the if/else including the entirely superfluous copy.

The two nested while loops (one for TDX_RND_NO_ENTROPY and the other for TDX_ERROR_SEPT_BUSY) also don't make for great code generation.

So, sure, the generated code here could be a better. But there's a lot more going on here than just shuffling gunk in and out of the 'struct tdx_module_args', and there's a _lot_ more work to do for one of these than for a plain old kvm_hypercall*().

It might make sense to separate out the "out" functionality into and maybe to uninline _some_ of the helper levels. But after that, there's not a lot of low hanging fruit.

#31 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

In reply to Sean Christopherson
Quoted 5 lines

then loads and zeros a ton of memory (tdx_kvm_hypercall()):

[...]

Quoted 5 lines

and then unpacks all of that memory back into registers, and reverses that last part on the way back, (__tdcall_saved_ret()):

[...]

Quoted 7 lines

It's honestly quite amusing, because y'all took one what I see as one of the big advantages of TDX over SEV (using registers instead of shared memory), and managed to effectively turn it into a disadvantage.

Again, I completely understand the maintenance and robustness benefits, but IMO the pendulum swung a bit too far in that direction.

Having to zero-and-init the structure and store output regs to the structure is an unfortunately burden if we want to have a single API __seamcall() for all SEAMCALL leafs.

To (precisely) avoid writing to the unnecessary structure members before and after the SEAMCALL instruction, we need to use your old way to have bunch of macros. But we may end up with *a lot* macros due to needing to cover new (e.g., live migration) SEAMCALLs.

Because essentially it's a game to implement wrappers for bunch of combinations of each individual input/output regs.

I don't want to judge which way is better, but to be honest I think completely switching to the old way (using bunch of macros) isn't a realistic option at this stage.

However, I think we might be able to change ...

    u64 __seamcall(u64 fn, struct tdx_module_args *args);

... to

u64 __seamcall(u64 fn, struct tdx_module_args *in,
struct tdx_module_args *out);

.. so that the assembly can actually skip "storing output regs to the structure" if the SEAMCALL doesn't really have any output regs except RAX.

I can try to do if you guys believe this should be done, and should be done earlier than later, but I am not sure _ANY_ optimization around SEAMCALL will have meaningful performance improvement.

#32 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 3 lines · Kai

I can try to do if you guys believe this should be done, and should be done earlier than later, but I am not sure _ANY_ optimization around SEAMCALL will have meaningful performance improvement.

I don't think Sean had performance concerns.

I think he was having a justifiably violent reaction to how much more complicated the generated code is to do a SEAMCALL versus a good ol' KVM hypercall.

"Complicated" in this case means lots more instructions and control flow. That's slower, sure, but the main impact is that when you go to debug it, it's *MUCH* harder to debug the SEAMCALL entry assembly than a KVM hypercall.

My takeaway from this, though, is that we are relying on the compiler for a *LOT*. There are also so many levels in the helpers that it's hard to avoid silly things like two _separate_ retry loops.

We should probably be looking at the generated code a _bit_ more often than never, and it's OK to tinker a _bit_ to make things out-of-line or make sure that the compiler optimizes everything that we think that it should.

Also remember that there are very fun tools out there that can make this much easier than recompiling the kernel a billion times:

	https://godbolt.org/z/8ooE4d465
#33 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 10 lines · Dave Hansen
Quoted 3 lines · Kai

I can try to do if you guys believe this should be done, and should be done earlier than later, but I am not sure _ANY_ optimization around SEAMCALL will have meaningful performance improvement.

I don't think Sean had performance concerns.

I think he was having a justifiably violent reaction to how much more complicated the generated code is to do a SEAMCALL versus a good ol' KVM hypercall.

Ah, I automatically linked "generating better code" to "in order to have better performance". My bad.

Quoted 5 lines

"Complicated" in this case means lots more instructions and control flow. That's slower, sure, but the main impact is that when you go to debug it, it's *MUCH* harder to debug the SEAMCALL entry assembly than a KVM hypercall.

[...]

Quoted 9 lines

My takeaway from this, though, is that we are relying on the compiler for a *LOT*. There are also so many levels in the helpers that it's hard to avoid silly things like two _separate_ retry loops.

We should probably be looking at the generated code a _bit_ more often than never, and it's OK to tinker a _bit_ to make things out-of-line or make sure that the compiler optimizes everything that we think that it should.

Yeah, agreed.

Quoted 5 lines

Also remember that there are very fun tools out there that can make this much easier than recompiling the kernel a billion times:

	https://godbolt.org/z/8ooE4d465

Ah, this is useful. Thanks for sharing :-)

#34 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

In reply to Dave Hansen
Quoted 10 lines · Dave Hansen
Quoted 3 lines · Kai

I can try to do if you guys believe this should be done, and should be done earlier than later, but I am not sure _ANY_ optimization around SEAMCALL will have meaningful performance improvement.

I don't think Sean had performance concerns.

I think he was having a justifiably violent reaction to how much more complicated the generated code is to do a SEAMCALL versus a good ol' KVM hypercall.

Yep. The code essentially violates the principle of least surprise. I genuinely thought I was dumping the wrong function(s) when I first looked at the output.

#35 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

In reply to Sean Christopherson
Quoted 2 lines · Sean Christopherson

So my feedback is to not worry about the exports, and instead focus on figuring out a way to make the generated code less bloated and easier to read/debug.

I think it was mistake trying to centralize TDCALL/SEAMCALL calls into few megawrappers. I think we can get better results by shifting leaf function wrappers into assembly.

We are going to have more assembly, but it should produce better result. Adding macros can help to write such wrapper and minimizer boilerplate.

Below is an example of how it can look like. It's not complete. I only converted TDCALLs, but TDVMCALLs or SEAMCALLs. TDVMCALLs are going to be more complex.

Any opinions? Is it something worth investing more time?

.set offset_rcx, TDX_MODULE_rcx
.set offset_rdx, TDX_MODULE_rdx
.set offset_r8, TDX_MODULE_r8
.set offset_r9, TDX_MODULE_r9
.set offset_r10, TDX_MODULE_r10
.set offset_r11, TDX_MODULE_r11

.macro save_output struct_reg regs:vararg
.irp reg,\regs

	movq	%\reg, offset_\reg(%\struct_reg)

.endr
.endm

.macro tdcall leaf

	movq	\leaf, %rax
	.byte	0x66,0x0f,0x01,0xcc

.endm

.macro tdcall_or_panic leaf
tdcall \leaf
testq %rax, %rax
jnz .Lpanic
.endm

SYM_FUNC_START(tdg_vm_rd)

	FRAME_BEGIN

	xorl	%ecx, %ecx
	movq	%rdi, %rdx

	tdcall_or_panic $TDG_VM_RD

	movq	%r8, %rax

	RET
	FRAME_END

SYM_FUNC_END(tdg_vm_rd)

SYM_FUNC_START(tdg_vm_wr)

	FRAME_BEGIN

	xorl	%ecx, %ecx
	movq	%rsi, %r8
	movq	%rdx, %r9
	movq	%rdi, %rdx

	tdcall_or_panic $TDG_VM_WR

	/* Old value */
	movq	%r8, %rax

	RET
	FRAME_END

SYM_FUNC_END(tdg_vm_wr)

SYM_FUNC_START(tdcs_ctls_set)

	FRAME_BEGIN

	movq	$TDCS_TD_CTLS, %rdx
	xorl	%ecx, %ecx
	movq	%rdi, %r8
	movq	%rdi, %r9

	tdcall $TDG_VM_WR

	testq	%rax, %rax
	setz	%al

	RET
	FRAME_END

SYM_FUNC_END(tdcs_ctls_set)

SYM_FUNC_START(tdg_sys_rd)

	FRAME_BEGIN

	xorl	%ecx, %ecx
	movq	%rdi, %rdx

	tdcall_or_panic $TDG_SYS_RD

	movq	%r8, %rax

	RET
	FRAME_END

SYM_FUNC_END(tdg_sys_rd)

SYM_FUNC_START(tdg_vp_veinfo_get)
FRAME_BEGIN

tdcall_or_panic $TDG_VP_VEINFO_GET

save_output struct_reg=rdi regs=rcx,rdx,r8,r9,r10

FRAME_END
RET
SYM_FUNC_END(tdg_vp_veinfo_get)

SYM_FUNC_START(tdg_vp_info)

	FRAME_BEGIN

	tdcall_or_panic $TDG_VP_INFO

	save_output struct_reg=rdi regs=rcx,rdx,r8,r9,r10,r11

	FRAME_END
	RET

SYM_FUNC_END(tdg_vp_info)

SYM_FUNC_START(tdg_mem_page_accept)

	FRAME_BEGIN

	movq	%rdi, %rcx

	tdcall $TDG_MEM_PAGE_ACCEPT

	FRAME_END
	RET

SYM_FUNC_END(tdg_mem_page_accept)

SYM_FUNC_START(tdg_mr_report)

	FRAME_BEGIN

	movq	%rdx, %r8
	movq	%rdi, %rcx
	movq	%rsi, %rdx

	tdcall $TDG_MR_REPORT

	FRAME_END
	RET

SYM_FUNC_END(tdg_mr_report)

.Lpanic:

	ud2
Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov
#36 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 9 lines · Kirill A. Shutemov
Quoted 3 lines · Sean Christopherson

So my feedback is to not worry about the exports, and instead focus on figuring
out a way to make the generated code less bloated and easier to read/debug.

I think it was mistake trying to centralize TDCALL/SEAMCALL calls into few megawrappers. I think we can get better results by shifting leaf function wrappers into assembly.

Quoted 8 lines

We are going to have more assembly, but it should produce better result. Adding macros can help to write such wrapper and minimizer boilerplate.

Below is an example of how it can look like. It's not complete. I only converted TDCALLs, but TDVMCALLs or SEAMCALLs. TDVMCALLs are going to be more complex.

Any opinions? Is it something worth investing more time?

We discussed offline how implementing these for each TDVM/SEAMCALL increases the chances of a bug in just one TDVM/SEAMCALL. Which could making debugging problems more challenging. Kirill raised the possibility of some code generating solution like cpufeatures.h, that could take a spec and generate correct calls.

So far no big wins have presented themselves. Kirill, do we think the path to move the messy part out-of-line will not work?

#37 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 19 lines · Rick P
Quoted 9 lines · Kirill A. Shutemov
Quoted 3 lines · Sean Christopherson

So my feedback is to not worry about the exports, and instead focus on figuring
out a way to make the generated code less bloated and easier to read/debug.

I think it was mistake trying to centralize TDCALL/SEAMCALL calls into few megawrappers. I think we can get better results by shifting leaf function wrappers into assembly.

Quoted 8 lines

We are going to have more assembly, but it should produce better result. Adding macros can help to write such wrapper and minimizer boilerplate.

Below is an example of how it can look like. It's not complete. I only converted TDCALLs, but TDVMCALLs or SEAMCALLs. TDVMCALLs are going to be more complex.

Any opinions? Is it something worth investing more time?

Quoted 7 lines

We discussed offline how implementing these for each TDVM/SEAMCALL increases the chances of a bug in just one TDVM/SEAMCALL. Which could making debugging problems more challenging. Kirill raised the possibility of some code generating solution like cpufeatures.h, that could take a spec and generate correct calls.

So far no big wins have presented themselves. Kirill, do we think the path to move the messy part out-of-line will not work?

I converted all TDCALL and TDVMCALL leafs to direct assembly wrappers. Here's WIP branch: https://github.com/intel/tdx/commits/guest-tdx-asm/

I still need to clean it up and write commit messages and comments for all wrappers.

Now I think it worth the shot.

Any feedback?

Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov
#38 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 31 lines · On Thu, Apr 18, 2024, [email protected] wrote:
Quoted 19 lines · Rick P
Quoted 9 lines · Kirill A. Shutemov
Quoted 3 lines · Sean Christopherson

So my feedback is to not worry about the exports, and instead focus on figuring
out a way to make the generated code less bloated and easier to read/debug.

I think it was mistake trying to centralize TDCALL/SEAMCALL calls into few megawrappers. I think we can get better results by shifting leaf function wrappers into assembly.

Quoted 8 lines

We are going to have more assembly, but it should produce better result. Adding macros can help to write such wrapper and minimizer boilerplate.

Below is an example of how it can look like. It's not complete. I only converted TDCALLs, but TDVMCALLs or SEAMCALLs. TDVMCALLs are going to be more complex.

Any opinions? Is it something worth investing more time?

Quoted 7 lines

We discussed offline how implementing these for each TDVM/SEAMCALL increases the chances of a bug in just one TDVM/SEAMCALL. Which could making debugging problems more challenging. Kirill raised the possibility of some code generating solution like cpufeatures.h, that could take a spec and generate correct calls.

So far no big wins have presented themselves. Kirill, do we think the path to move the messy part out-of-line will not work?

I converted all TDCALL and TDVMCALL leafs to direct assembly wrappers. Here's WIP branch: https://github.com/intel/tdx/commits/guest-tdx-asm/

Quoted 6 lines

I still need to clean it up and write commit messages and comments for all wrappers.

Now I think it worth the shot.

Any feedback?

I find it hard to review for correctness, and extremely susceptible to developer error. E.g. lots of copy+paste, and manual encoding of RCX to expose registers. It also bleeds TDX ABI into C code, e.g.

	/*
	 * As per TDX GHCI CPUID ABI, r12-r15 registers contain contents of
	 * EAX, EBX, ECX, EDX registers after the CPUID instruction execution.
	 * So copy the register contents back to pt_regs.
	 */
	regs->ax = args.r12;
	regs->bx = args.r13;
	regs->cx = args.r14;
	regs->dx = args.r15;

Oh, and it requires input/output paramters, which is quite gross for C code *and* for assembly code, e.g.

	u64 tdvmcall_map_gpa(u64 *gpa, u64 size);

and then the accompanying assembly code:

		FRAME_BEGIN

		save_regs r12,r13

		movq	(%rdi), %r12
		movq	%rsi, %r13

		movq	$(TDX_R10 | TDX_R11 | TDX_R12 | TDX_R13), %rcx

		tdvmcall	$TDVMCALL_MAP_GPA

		movq	%r11, (%rdi)

		restore_regs r13,r12

		FRAME_END
		RET

I think having one trampoline makes sense, e.g. to minimize the probability of leaking register state to the VMM. The part that I don't like, and which generates awful code, is shoving register state into a memory structure.

The annoying part with the TDX ABI is that it heavily uses r8-r15, and asm() constraints don't play nice with r8-15. But that doesn't mean we can't use asm() with macros, it just means we have to play games with registers.

Because REG-REG moves are super cheap, and ignoring the fatal error goofiness, there are at most four inputs. That means having a single trampoline take *all* possible inputs is a non-issue. And we can avoiding polluting the inline code if we bury the register shuffling in the trampoline.

And if we use asm() wrappers to call the trampoline, then the trampoline doesn't need to precisely follow the C calling convention. I.e. the trampoline can return with the outputs still in r12-r15, and let the asm() wrappers extract the outputs they want.

As it stands, TDVMCALLs either have 0, 1, or 4 outputs. I.e. we only need three asm() wrappers. We could get away with one wrapper, but then users of the wrappers would need dummy variables for inputs *and* outputs, and the outputs get gross.

Completely untested, but this is what I'm thinking. Side topic, I think making "tdcall" a macro that takes a leaf is a mistake. If/when an assembler learns what tdcall is, we're going to have to rewrite all of that code. And what a coincidence, my suggestion needs a bare TDCALL! :-)

Side topic #2, I don't think the trampoline needs a stack frame, its a leaf function.

Side topic #3, the ud2 to induce panic should be out-of-line.

Weird? Yeah. But at least we one need to document one weird calling convention, and the ugliness is contained to three macros and a small assembly function.

.pushsection .noinstr.text, "ax"
SYM_FUNC_START(tdvmcall_trampoline)

	movq	$TDX_HYPERCALL_STANDARD, %r10
	movq	%rax, %r11
	movq	%rdi, %r12
	movq	%rsi, %r13
	movq	%rdx, %r14
	movq	%rcx, %r15

	movq	$(TDX_R10 | TDX_R11 | TDX_R12 | TDX_R13 | TDX_R14 | TDX_R15), %rcx

	tdcall

	testq	%rax, %rax
	jnz	.Lpanic

	ret

.Lpanic:

	ud2

SYM_FUNC_END(tdvmcall_trampoline)
.popsection

#define TDVMCALL(reason, in1, in2, in3, in4) \

({									\
	long __ret;							\
									\
	asm(								\
		"call tdvmcall_trampoline\n\t"				\
		"mov %%r10, %0\n\t"					\
		: "=r" (__ret)						\
		: "D" (in1), "S"(in2), "d"(in3), "c" (in4)		\
		: "r12", "r13", "r14", "r15"				\
	);								\
	__ret;								\
})

#define TDVMCALL_1(reason, in1, in2, in3, in4, out1) \

({									\
	long __ret;							\
									\
	asm(								\
		"call tdvmcall_trampoline\n\t"				\
		"mov %%r10, %0\n\t"					\
		"mov %%r12, %1\n\t"					\
		: "=r"(__ret) "=r" (out1)				\
		: "a"(reason), "D" (in1), "S"(in2), "d"(in3), "c" (in4)	\
		: "r12", "r13", "r14", "r15"				\
	);								\
	__ret;								\
})

#define TDVMCALL_4(reason, in1, in2, in3, in4, out1, out2, out3, out4) \

({									\
	long __ret;							\
									\
	asm(								\
		"call tdvmcall_trampoline\n\t"				\
		"mov %%r10, %0\n\t"					\
		"mov %%r12, %1\n\t"					\
		"mov %%r13, %2\n\t"					\
		"mov %%r14, %3\n\t"					\
		"mov %%r15, %4\n\t"					\
		: "=r" (__ret),						\
		  "=r" (out1), "=r" (out2), "=r" (out3), "=r" (out4)	\
		: "a"(reason), "D" (in1), "S"(in2), "d"(in3), "c" (in4)	\
		  [reason] "i" (reason) 				\
		: "r12", "r13", "r14", "r15"				\
	);								\
	__ret;								\
})

static int handle_halt(struct ve_info *ve)

{
	if (TDVMCALL(EXIT_REASON_HALT, irqs_disabled(), 0, 0, 0))
		return -EIO;

	return ve_instr_len(ve);
}

void __cpuidle tdx_safe_halt(void)

{
	WARN_ONCE(TDVMCALL(EXIT_REASON_HALT, false, 0, 0, 0),
		  "HLT instruction emulation failed");
}

static int read_msr(struct pt_regs *regs, struct ve_info *ve)

{
	u64 val;

	if (TDVMCALL_1(EXIT_REASON_MSR_READ, regs->cx, 0, 0, 0, val))
		return -EIO;

	regs->ax = lower_32_bits(val);
	regs->dx = upper_32_bits(val);

	return ve_instr_len(ve);
}

static int write_msr(struct pt_regs *regs, struct ve_info *ve)

{
	u64 val = (u64)regs->dx << 32 | regs->ax;

	if (TDVMCALL(EXIT_REASON_MSR_WRITE, regs->cx, val, 0, 0))
		return -EIO;

	return ve_instr_len(ve);
}
static int handle_cpuid(struct pt_regs *regs, struct ve_info *ve)
{
	/*
	 * Only allow VMM to control range reserved for hypervisor
	 * communication.
	 *
	 * Return all-zeros for any CPUID outside the range. It matches CPU
	 * behaviour for non-supported leaf.
	 */
	if (regs->ax < 0x40000000 || regs->ax > 0x4FFFFFFF) {
		regs->ax = regs->bx = regs->cx = regs->dx = 0;
		return ve_instr_len(ve);
	}

	if (TDVMCALL_4(EXIT_REASON_CPUID, regs->ax, regs->cx, 0, 0,
		       regs->ax, regs->bx, regs->cx, regs->dx))
		return -EIO;

	return ve_instr_len(ve);
}

static bool mmio_read(int size, u64 gpa, u64 *val)

{
	*val = 0;
	return !TDVMCALL_1(EXIT_REASON_EPT_VIOLATION, size, EPT_READ, gpa, 0, val);
}

static bool mmio_write(int size, u64 gpa, u64 val)

{
	return !TDVMCALL(EXIT_REASON_EPT_VIOLATION, size, EPT_WRITE, gpa, val);
}
#39 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 35 lines · Sean Christopherson
Quoted 34 lines · On Thu, Apr 18, 2024, [email protected] wrote:
Quoted 19 lines · Rick P
Quoted 9 lines · Kirill A. Shutemov
Quoted 3 lines · Sean Christopherson

So my feedback is to not worry about the exports, and instead focus on figuring
out a way to make the generated code less bloated and easier to read/debug.

I think it was mistake trying to centralize TDCALL/SEAMCALL calls into few megawrappers. I think we can get better results by shifting leaf function wrappers into assembly.

Quoted 8 lines

We are going to have more assembly, but it should produce better result. Adding macros can help to write such wrapper and minimizer boilerplate.

Below is an example of how it can look like. It's not complete. I only converted TDCALLs, but TDVMCALLs or SEAMCALLs. TDVMCALLs are going to be more complex.

Any opinions? Is it something worth investing more time?

Quoted 7 lines

We discussed offline how implementing these for each TDVM/SEAMCALL increases the chances of a bug in just one TDVM/SEAMCALL. Which could making debugging problems more challenging. Kirill raised the possibility of some code generating solution like cpufeatures.h, that could take a spec and generate correct calls.

So far no big wins have presented themselves. Kirill, do we think the path to move the messy part out-of-line will not work?

I converted all TDCALL and TDVMCALL leafs to direct assembly wrappers. Here's WIP branch: https://github.com/intel/tdx/commits/guest-tdx-asm/

I still need to clean it up and write commit messages and comments for all wrappers.

Quoted 6 lines
Quoted 3 lines

Now I think it worth the shot.

Any feedback?

I find it hard to review for correctness, and extremely susceptible to developer error. E.g. lots of copy+paste, and manual encoding of RCX to expose registers.

Yes, I agree. The approach requires careful manual work and is error-prone. I was planning to get around and stare at every wrapper to make sure it is correct. This approach is not scalable, though.

Quoted 37 lines

It also bleeds TDX ABI into C code, e.g.

	/*
	 * As per TDX GHCI CPUID ABI, r12-r15 registers contain contents of
	 * EAX, EBX, ECX, EDX registers after the CPUID instruction execution.
	 * So copy the register contents back to pt_regs.
	 */
	regs->ax = args.r12;
	regs->bx = args.r13;
	regs->cx = args.r14;
	regs->dx = args.r15;

Oh, and it requires input/output paramters, which is quite gross for C code *and* for assembly code, e.g.

	u64 tdvmcall_map_gpa(u64 *gpa, u64 size);

and then the accompanying assembly code:

		FRAME_BEGIN

		save_regs r12,r13

		movq	(%rdi), %r12
		movq	%rsi, %r13

		movq	$(TDX_R10 | TDX_R11 | TDX_R12 | TDX_R13), %rcx

		tdvmcall	$TDVMCALL_MAP_GPA

		movq	%r11, (%rdi)

		restore_regs r13,r12

		FRAME_END
		RET
Quoted 3 lines

I think having one trampoline makes sense, e.g. to minimize the probability of leaking register state to the VMM. The part that I don't like, and which generates awful code, is shoving register state into a memory structure.

I don't think we can get away with single trampoline. We have outliers.

See TDG.VP.VMCALL<ReportFatalError> that uses pretty much all registers as input. And I hope we wouldn't need TDG.VP.VMCALL<Instruction.PCONFIG> any time soon. It uses all possible output registers.

But I guess we can make a *few* wrappers that covers all needed cases.

Quoted 14 lines

The annoying part with the TDX ABI is that it heavily uses r8-r15, and asm() constraints don't play nice with r8-15. But that doesn't mean we can't use asm() with macros, it just means we have to play games with registers.

Because REG-REG moves are super cheap, and ignoring the fatal error goofiness, there are at most four inputs. That means having a single trampoline take *all* possible inputs is a non-issue. And we can avoiding polluting the inline code if we bury the register shuffling in the trampoline.

And if we use asm() wrappers to call the trampoline, then the trampoline doesn't need to precisely follow the C calling convention. I.e. the trampoline can return with the outputs still in r12-r15, and let the asm() wrappers extract the outputs they want.

Quoted 8 lines

As it stands, TDVMCALLs either have 0, 1, or 4 outputs. I.e. we only need three asm() wrappers. We could get away with one wrapper, but then users of the wrappers would need dummy variables for inputs *and* outputs, and the outputs get gross.

Completely untested, but this is what I'm thinking. Side topic, I think making "tdcall" a macro that takes a leaf is a mistake. If/when an assembler learns what tdcall is, we're going to have to rewrite all of that code. And what a coincidence, my suggestion needs a bare TDCALL! :-)

I guess rename the macro (if it still needed) to something like tdcall_leaf or something.

Quoted 1 line

Side topic #2, I don't think the trampoline needs a stack frame, its a leaf function.

Hm. I guess.

Quoted 1 line

Side topic #3, the ud2 to induce panic should be out-of-line.

Yeah. I switched to the inline one while debugging one section mismatch issue and forgot to switch back.

Quoted 2 lines

Weird? Yeah. But at least we one need to document one weird calling convention, and the ugliness is contained to three macros and a small assembly function.

Okay, the approach is worth exploring. I can work on it.

You focuses here on TDVMCALL. What is your take on the rest of TDCALL?

Quoted 38 lines

.pushsection .noinstr.text, "ax"
SYM_FUNC_START(tdvmcall_trampoline)

	movq	$TDX_HYPERCALL_STANDARD, %r10
	movq	%rax, %r11
	movq	%rdi, %r12
	movq	%rsi, %r13
	movq	%rdx, %r14
	movq	%rcx, %r15

	movq	$(TDX_R10 | TDX_R11 | TDX_R12 | TDX_R13 | TDX_R14 | TDX_R15), %rcx

	tdcall

	testq	%rax, %rax
	jnz	.Lpanic

	ret

.Lpanic:

	ud2

SYM_FUNC_END(tdvmcall_trampoline)
.popsection

#define TDVMCALL(reason, in1, in2, in3, in4) \

({									\
	long __ret;							\
									\
	asm(								\
		"call tdvmcall_trampoline\n\t"				\
		"mov %%r10, %0\n\t"					\
		: "=r" (__ret)						\
		: "D" (in1), "S"(in2), "d"(in3), "c" (in4)		\
		: "r12", "r13", "r14", "r15"				\
	);								\
	__ret;								\
})
Quoted 8 lines

#define TDVMCALL_1(reason, in1, in2, in3, in4, out1) \

({									\
	long __ret;							\
									\
	asm(								\
		"call tdvmcall_trampoline\n\t"				\
		"mov %%r10, %0\n\t"					\
		"mov %%r12, %1\n\t"					\

It is r11, not r12.

Quoted 94 lines
		: "=r"(__ret) "=r" (out1)				\
		: "a"(reason), "D" (in1), "S"(in2), "d"(in3), "c" (in4)	\
		: "r12", "r13", "r14", "r15"				\
	);								\
	__ret;								\
})

#define TDVMCALL_4(reason, in1, in2, in3, in4, out1, out2, out3, out4) \

({									\
	long __ret;							\
									\
	asm(								\
		"call tdvmcall_trampoline\n\t"				\
		"mov %%r10, %0\n\t"					\
		"mov %%r12, %1\n\t"					\
		"mov %%r13, %2\n\t"					\
		"mov %%r14, %3\n\t"					\
		"mov %%r15, %4\n\t"					\
		: "=r" (__ret),						\
		  "=r" (out1), "=r" (out2), "=r" (out3), "=r" (out4)	\
		: "a"(reason), "D" (in1), "S"(in2), "d"(in3), "c" (in4)	\
		  [reason] "i" (reason) 				\
		: "r12", "r13", "r14", "r15"				\
	);								\
	__ret;								\
})

static int handle_halt(struct ve_info *ve)

{
	if (TDVMCALL(EXIT_REASON_HALT, irqs_disabled(), 0, 0, 0))
		return -EIO;

	return ve_instr_len(ve);
}

void __cpuidle tdx_safe_halt(void)

{
	WARN_ONCE(TDVMCALL(EXIT_REASON_HALT, false, 0, 0, 0),
		  "HLT instruction emulation failed");
}

static int read_msr(struct pt_regs *regs, struct ve_info *ve)

{
	u64 val;

	if (TDVMCALL_1(EXIT_REASON_MSR_READ, regs->cx, 0, 0, 0, val))
		return -EIO;

	regs->ax = lower_32_bits(val);
	regs->dx = upper_32_bits(val);

	return ve_instr_len(ve);
}

static int write_msr(struct pt_regs *regs, struct ve_info *ve)

{
	u64 val = (u64)regs->dx << 32 | regs->ax;

	if (TDVMCALL(EXIT_REASON_MSR_WRITE, regs->cx, val, 0, 0))
		return -EIO;

	return ve_instr_len(ve);
}
static int handle_cpuid(struct pt_regs *regs, struct ve_info *ve)
{
	/*
	 * Only allow VMM to control range reserved for hypervisor
	 * communication.
	 *
	 * Return all-zeros for any CPUID outside the range. It matches CPU
	 * behaviour for non-supported leaf.
	 */
	if (regs->ax < 0x40000000 || regs->ax > 0x4FFFFFFF) {
		regs->ax = regs->bx = regs->cx = regs->dx = 0;
		return ve_instr_len(ve);
	}

	if (TDVMCALL_4(EXIT_REASON_CPUID, regs->ax, regs->cx, 0, 0,
		       regs->ax, regs->bx, regs->cx, regs->dx))
		return -EIO;

	return ve_instr_len(ve);
}

static bool mmio_read(int size, u64 gpa, u64 *val)

{
	*val = 0;
	return !TDVMCALL_1(EXIT_REASON_EPT_VIOLATION, size, EPT_READ, gpa, 0, val);
}

static bool mmio_write(int size, u64 gpa, u64 val)

{
	return !TDVMCALL(EXIT_REASON_EPT_VIOLATION, size, EPT_WRITE, gpa, val);
}
Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov
#40 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 7 lines · On Fri, Apr 19, 2024, [email protected] wrote:
Quoted 4 lines · Sean Christopherson

On Thu, Apr 18, 2024, [email protected] wrote:
I think having one trampoline makes sense, e.g. to minimize the probability of leaking register state to the VMM. The part that I don't like, and which generates awful code, is shoving register state into a memory structure.

I don't think we can get away with single trampoline. We have outliers.

Yeah, I should have said "one trampoline for all of the not insane APIs" :-)

Quoted 3 lines

See TDG.VP.VMCALL<ReportFatalError> that uses pretty much all registers as input. And I hope we wouldn't need TDG.VP.VMCALL<Instruction.PCONFIG> any time soon. It uses all possible output registers.

XMM: just say no.

Quoted 1 line

But I guess we can make a *few* wrappers that covers all needed cases.

Yeah. I suspect two will suffice. One for the calls that say at or below four inputs, and one for the fat ones like ReportFatalError that use everything under the sun.

For the latter one, marshalling data into registers via an in-memory structure makes, especially if we move away from tdx_module_args, e.g. this is quite clean and reasonable:

	union {
		/* Define register order according to the GHCI */
		struct { u64 r14, r15, rbx, rdi, rsi, r8, r9, rdx; };

		char str[64];
	} message;

	/* VMM assumes '\0' in byte 65, if the message took all 64 bytes */
	strtomem_pad(message.str, msg, '\0');

	/*
	 * This hypercall should never return and it is not safe
	 * to keep the guest running. Call it forever if it
	 * happens to return.
	 */
	while (1)
		tdx_fat_tdvmcall(&message);
Quoted 6 lines
Quoted 2 lines

Weird? Yeah. But at least we one need to document one weird calling convention, and the ugliness is contained to three macros and a small assembly function.

Okay, the approach is worth exploring. I can work on it.

You focuses here on TDVMCALL. What is your take on the rest of TDCALL?

Not sure, haven't looked at them recently. At a glance, something similar? The use of high registers instead of RDI and RSI is damn annoying :-/

Hmm, but it looks like there are enough simple TDCALLs that stay away from high registers that open coding inline asm() is a viable (best?) approach.

RAX being the leaf and the return value is annoying, so maybe a simple macro to make it easier to deal with that? It won't allow for perfectly optimal code generation, but forcing a MOV for a TDCALL isn't going to affect performance, and it will make reading the code dead simple.

  #define tdcall_leaf(leaf) "mov $" leaf ", %%eax\n\t.byte 0x66,0x0f,0x01,0xcc\n\t"

Then PAGE_ACCEPT is simply:

	asm(tdcall_leaf(TDG_MEM_PAGE_ACCEPT)
	    : "=a"(ret),
            : "c"(start | page_size));
	if (ret)
		return 0;

And even the meanies that use R8 are reasonably easy to handle:

	asm("xor %%r8d, %%r8d\n\t"
	    tdcall_leaf(TDG_MR_REPORT)
	    : "=a"(ret)
	    : "c"(__pa(report)), "d"(__pa(data)));

and (though using names for the outputs, I just can't remember the syntax as I'm typing this :-/)

	asm(tdcall_leaf(TDG_VM_RD)
	    "mov %%r8, %0\n\t"
	    : "=r"(value), "=a"(ret)
	    : "c"(0), "d"(no_idea_what_this_is));

	if (ret)
		<cry>

	return value;

Or, if you wanted to get fancy, use asm_goto_output() to bail on an error so that you could optimize for using RAX as the return value, because *that's going to make all the difference :-D

#41 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 5 lines · Sean Christopherson
Quoted 1 line

But I guess we can make a *few* wrappers that covers all needed cases.

Yeah. I suspect two will suffice. One for the calls that say at or below four inputs, and one for the fat ones like ReportFatalError that use everything under the sun.

I ended up with three helpers:

- tdvmcall_trampoline() as you proposed.

- tdvmcall_report_fatal_error() that does ReportFatalError specificly. Pointer to char array as an input. Never returns: no need to save/restore registers.

- hv_tdx_hypercall(). Hyper-V annoyingly uses R8 and RDX as parameters :/

Quoted 2 lines

Not sure, haven't looked at them recently. At a glance, something similar? The use of high registers instead of RDI and RSI is damn annoying :-/

I defined three helpers: TDCALL_0(), TDCALL_1() and TDCALL_5(). All takes 4 input arguments.

I've updated the WIP branch with the new version:

https://github.com/intel/tdx/commits/guest-tdx-asm/

Any feedback is welcome.

Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov
#42 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

In reply to [email protected]
Quoted 5 lines · On Fri, 2024-04-19 at 17:46 +0300, [email protected] wrote:
Quoted 2 lines

Side topic #3, the ud2 to induce panic should be out-of-line.

Yeah. I switched to the inline one while debugging one section mismatch issue and forgot to switch back.

Sorry, why do we need to panic?

#43 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 8 lines · Rick P
Quoted 5 lines · On Fri, 2024-04-19 at 17:46 +0300, [email protected] wrote:
Quoted 2 lines

Side topic #3, the ud2 to induce panic should be out-of-line.

Yeah. I switched to the inline one while debugging one section mismatch issue and forgot to switch back.

Sorry, why do we need to panic?

It panics in cases that should never occur if the TDX module is functioning properly. For example, TDVMCALL itself should never fail, although the leaf function could.

Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov
#44 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 5 lines · On Mon, 2024-04-22 at 14:46 +0300, [email protected] wrote:
Quoted 4 lines · Rick P
Quoted 3 lines · On Fri, 2024-04-19 at 17:46 +0300, [email protected] wrote:
Quoted 2 lines

Side topic #3, the ud2 to induce panic should be out-of-line.

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines

Yeah. I switched to the inline one while debugging one section mismatch issue and forgot to switch back.

Sorry, why do we need to panic?

It panics in cases that should never occur if the TDX module is functioning properly. For example, TDVMCALL itself should never fail, although the leaf function could.

Panic should normally be for desperate situations when horrible things will likely happen if we continue, right? Why are we adding a panic when we didn't have one before? Is it a second change, or a side affect of the refactor?

#45 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 11 lines · Rick P Edgecombe
Quoted 10 lines · On Mon, 2024-04-22 at 14:46 +0300, [email protected] wrote:
Quoted 8 lines · Rick P
Quoted 5 lines · On Fri, 2024-04-19 at 17:46 +0300, [email protected] wrote:
Quoted 2 lines

Side topic #3, the ud2 to induce panic should be out-of-line.

Yeah. I switched to the inline one while debugging one section mismatch issue and forgot to switch back.

Sorry, why do we need to panic?

Quoted 7 lines
Quoted 3 lines

It panics in cases that should never occur if the TDX module is functioning properly. For example, TDVMCALL itself should never fail, although the leaf function could.

Panic should normally be for desperate situations when horrible things will likely happen if we continue, right? Why are we adding a panic when we didn't have one before? Is it a second change, or a side affect of the refactor?

The kernel already does panic() if TDCALL itself fails,

static inline void tdcall(u64 fn, struct tdx_module_args *args)

  {
	if (__tdcall_ret(fn, args))
		panic("TDCALL %lld failed (Buggy TDX module!)\n", fn);

}

/* Called from __tdx_hypercall() for unrecoverable failure */ noinstr void __noreturn __tdx_hypercall_failed(void)

  {
	instrumentation_begin();
	panic("TDVMCALL failed. TDX module bug?");

}

it's just doesn in C code via panic(), not in asm via a bare ud2.

#46 lore raw

Re: [PATCH v19 007/130] x86/virt/tdx: Export SEAMCALL functions

Quoted 8 lines · Sean Christopherson

The kernel already does panic() if TDCALL itself fails,

  static inline void tdcall(u64 fn, struct tdx_module_args *args) {
        if (__tdcall_ret(fn, args))
                panic("TDCALL %lld failed (Buggy TDX module!)\n", fn); }

Quoted 8 lines

  /* Called from __tdx_hypercall() for unrecoverable failure */ noinstr void __noreturn __tdx_hypercall_failed(void) {
        instrumentation_begin();
        panic("TDVMCALL failed. TDX module bug?");
  }

it's just doesn in C code via panic(), not in asm via a bare ud2.

Hmm, I didn't realize. It looks like today some calls do and some don't. I don't mean to reopen old debates. Just surprised that these are able to bring down the system. Which funnily enough connects back to the original issue of the patch:
whether they are safe to export for module use.

#47 lore raw

PATCH v19 8/130 x86/tdx: Warning with 32bit build shift-count-overflow

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

This patch fixes the following warnings.

In file included from arch/x86/kernel/asm-offsets.c:22:
arch/x86/include/asm/tdx.h:92:87: warning: shift count >= width of type [-Wshift-count-overflow] arch/x86/include/asm/tdx.h:20:21: note: expanded from macro 'TDX_ERROR'
#define TDX_ERROR _BITUL(63)

                                           ^~~~~~~~~~

Also consistently use ULL for TDX_SEAMCALL_VMFAILINVALID.

Fixes 527a534c7326 ("x86/tdx: Provide common base for SEAMCALL and TDCALL C wrappers")
---
 arch/x86/include/asm/tdx.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
arch/x86/include/asm/tdx.h +2 -2
index 16be3a1e4916..1e9dcdf9912b 100644
@@ -17,9 +17,9 @@
  * Bits 47:40 == 0xFF indicate Reserved status code class that never used by
  * TDX module.
  */
-#define TDX_ERROR			_BITUL(63)
+#define TDX_ERROR			_BITULL(63)
 #define TDX_SW_ERROR			(TDX_ERROR | GENMASK_ULL(47, 40))
-#define TDX_SEAMCALL_VMFAILINVALID	(TDX_SW_ERROR | _UL(0xFFFF0000))
+#define TDX_SEAMCALL_VMFAILINVALID	(TDX_SW_ERROR | _ULL(0xFFFF0000))
 
 #define TDX_SEAMCALL_GP			(TDX_SW_ERROR | X86_TRAP_GP)
 #define TDX_SEAMCALL_UD			(TDX_SW_ERROR | X86_TRAP_UD)
Signature · 1 line
2.25.1
#48 lore raw

Re: [PATCH v19 008/130] x86/tdx: Warning with 32bit build shift-count-overflow

Quoted 11 lines · On 26/02/2024 9:25 pm, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

This patch fixes the following warnings.

    In file included from arch/x86/kernel/asm-offsets.c:22:
    arch/x86/include/asm/tdx.h:92:87: warning: shift count >= width of type [-Wshift-count-overflow]
    arch/x86/include/asm/tdx.h:20:21: note: expanded from macro 'TDX_ERROR'
    #define TDX_ERROR                       _BITUL(63)

                                            ^~~~~~~~~~
Quoted 3 lines

Also consistently use ULL for TDX_SEAMCALL_VMFAILINVALID.

Fixes 527a534c7326 ("x86/tdx: Provide common base for SEAMCALL and TDCALL C wrappers")

+Kirill.

This kinda fix should be sent out as a separate patch.

Quoted 11 lines
---
  arch/x86/include/asm/tdx.h | 4 ++--
  1 file changed, 2 insertions(+), 2 deletions(-)
arch/x86/include/asm/tdx.h +0 -0
index 16be3a1e4916..1e9dcdf9912b 100644
@@ -17,9 +17,9 @@
   * Bits 47:40 == 0xFF indicate Reserved status code class that never used by

arch/x86/include/asm/tdx.h line 25

Quoted 10 lines
arch/x86/include/asm/tdx.h +2 -2
   * TDX module.
   */
-#define TDX_ERROR			_BITUL(63)
+#define TDX_ERROR			_BITULL(63)
  #define TDX_SW_ERROR			(TDX_ERROR | GENMASK_ULL(47, 40))
-#define TDX_SEAMCALL_VMFAILINVALID	(TDX_SW_ERROR | _UL(0xFFFF0000))
+#define TDX_SEAMCALL_VMFAILINVALID	(TDX_SW_ERROR | _ULL(0xFFFF0000))
 
  #define TDX_SEAMCALL_GP			(TDX_SW_ERROR | X86_TRAP_GP)
  #define TDX_SEAMCALL_UD			(TDX_SW_ERROR | X86_TRAP_UD)

Both TDX guest and TDX host code depends on X86_64 in the Kconfig. This issue seems due to asm-offsets.c includes <asm/tdx.h> unconditionally.

It doesn't make sense to generate any TDX related code in asm-offsets.h so I am wondering whether it is better to just make the inclusion of <asm/tdx.h> conditionally or move it the asm-offsets_64.c?

Kirill what's your opinion?

Btw after quick try seems I cannot reproduce this (w/o this KVM TDX patchset). Isaku, could you share your .config?

#49 lore raw

Re: [PATCH v19 008/130] x86/tdx: Warning with 32bit build shift-count-overflow

Quoted 7 lines · Kai
Quoted 4 lines · On 26/02/2024 9:25 pm, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

This patch fixes the following warnings.

Quoted 7 lines
Quoted 7 lines
    In file included from arch/x86/kernel/asm-offsets.c:22:
    arch/x86/include/asm/tdx.h:92:87: warning: shift count >= width of type [-Wshift-count-overflow]
    arch/x86/include/asm/tdx.h:20:21: note: expanded from macro 'TDX_ERROR'
    #define TDX_ERROR                       _BITUL(63)

                                            ^~~~~~~~~~

I think you trim the warning message. I don't see the actual user of the define. Define itself will not generate the warning. You need to actually use it outside of preprocessor. I don't understand who would use it in 32-bit code. Maybe fixing it this way masking other issue.

That said, I don't object the change itself. We just need to understand the context more.

Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov
#50 lore raw

Re: [PATCH v19 008/130] x86/tdx: Warning with 32bit build shift-count-overflow

Quoted 16 lines · Kirill A. Shutemov
Quoted 7 lines · Kai
Quoted 4 lines · On 26/02/2024 9:25 pm, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

This patch fixes the following warnings.

Quoted 7 lines
Quoted 7 lines
    In file included from arch/x86/kernel/asm-offsets.c:22:
    arch/x86/include/asm/tdx.h:92:87: warning: shift count >= width of type [-Wshift-count-overflow]
    arch/x86/include/asm/tdx.h:20:21: note: expanded from macro 'TDX_ERROR'
    #define TDX_ERROR                       _BITUL(63)

                                            ^~~~~~~~~~
Quoted 7 lines

I think you trim the warning message. I don't see the actual user of the define. Define itself will not generate the warning. You need to actually use it outside of preprocessor. I don't understand who would use it in 32-bit code. Maybe fixing it this way masking other issue.

That said, I don't object the change itself. We just need to understand the context more.

v18 used it as stub function. v19 dropped it as the stub was not needed.

Signature · 1 line
Isaku Yamahata <[email protected]>
#51 lore raw

Re: [PATCH v19 008/130] x86/tdx: Warning with 32bit build shift-count-overflow

Quoted 24 lines · Isaku Yamahata
Quoted 21 lines · Kirill A. Shutemov
Quoted 7 lines · Kai
Quoted 4 lines · On 26/02/2024 9:25 pm, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

This patch fixes the following warnings.

Quoted 7 lines
Quoted 7 lines
     In file included from arch/x86/kernel/asm-offsets.c:22:
     arch/x86/include/asm/tdx.h:92:87: warning: shift count >= width of type [-Wshift-count-overflow]
     arch/x86/include/asm/tdx.h:20:21: note: expanded from macro 'TDX_ERROR'
     #define TDX_ERROR                       _BITUL(63)

                                             ^~~~~~~~~~

I think you trim the warning message. I don't see the actual user of the define. Define itself will not generate the warning. You need to actually use it outside of preprocessor. I don't understand who would use it in 32-bit code. Maybe fixing it this way masking other issue.

Quoted 4 lines
Quoted 2 lines

That said, I don't object the change itself. We just need to understand the context more.

v18 used it as stub function. v19 dropped it as the stub was not needed.

Sorry I literally don't understand what you are talking about here.

Please just clarify (at least):

- Does this problem exist in upstream code?
- If it does, what is the root cause, and how to reproduce?

#52 lore raw

Re: [PATCH v19 008/130] x86/tdx: Warning with 32bit build shift-count-overflow

Quoted 30 lines · Kai
Quoted 27 lines · Isaku Yamahata
Quoted 23 lines · Kirill A. Shutemov
Quoted 7 lines · Kai
Quoted 4 lines · On 26/02/2024 9:25 pm, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

This patch fixes the following warnings.

Quoted 7 lines
Quoted 7 lines
     In file included from arch/x86/kernel/asm-offsets.c:22:
     arch/x86/include/asm/tdx.h:92:87: warning: shift count >= width of type [-Wshift-count-overflow]
     arch/x86/include/asm/tdx.h:20:21: note: expanded from macro 'TDX_ERROR'
     #define TDX_ERROR                       _BITUL(63)

                                             ^~~~~~~~~~

I think you trim the warning message. I don't see the actual user of the define. Define itself will not generate the warning. You need to actually use it outside of preprocessor. I don't understand who would use it in 32-bit code. Maybe fixing it this way masking other issue.

That said, I don't object the change itself. We just need to understand the context more.

Quoted 7 lines
Quoted 1 line

v18 used it as stub function. v19 dropped it as the stub was not needed.

Sorry I literally don't understand what you are talking about here.

Please just clarify (at least):

- Does this problem exist in upstream code?

No.

Quoted 1 line

- If it does, what is the root cause, and how to reproduce?

v18 had a problem because it has stub function. v19 doesn't have problem because it deleted the stub function.

Signature · 1 line
Isaku Yamahata <[email protected]>
#53 lore raw

Re: [PATCH v19 008/130] x86/tdx: Warning with 32bit build shift-count-overflow

Quoted 11 lines
Quoted 4 lines

Please just clarify (at least):

- Does this problem exist in upstream code?

No.

Quoted 1 line

- If it does, what is the root cause, and how to reproduce?

v18 had a problem because it has stub function. v19 doesn't have problem because it deleted the stub function.

What is the "stub function"??

If "v19 doesn't have problem", why do you even _need_ this patch??

I am tired of guessing, but I don't care anymore given it's not a problem in upstream code.

#54 lore raw

PATCH v19 9/130 KVM: x86: Add gmem hook for determining max NPT mapping level

In reply to [email protected]

From: Michael Roth <[email protected]>

In the case of SEV-SNP, whether or not a 2MB page can be mapped via a 2MB mapping in the guest's nested page table depends on whether or not any subpages within the range have already been initialized as private in the RMP table. The existing mixed-attribute tracking in KVM is insufficient here, for instance:

- gmem allocates 2MB page
- guest issues PVALIDATE on 2MB page
- guest later converts a subpage to shared
- SNP host code issues PSMASH to split 2MB RMP mapping to 4K
- KVM MMU splits NPT mapping to 4K

At this point there are no mixed attributes, and KVM would normally allow for 2MB NPT mappings again, but this is actually not allowed because the RMP table mappings are 4K and cannot be promoted on the hypervisor side, so the NPT mappings must still be limited to 4K to match this.

Add a hook to determine the max NPT mapping size in situations like this.

---
 arch/x86/include/asm/kvm-x86-ops.h |  1 +
 arch/x86/include/asm/kvm_host.h    |  2 ++
 arch/x86/kvm/mmu/mmu.c             | 12 ++++++++++--
 3 files changed, 13 insertions(+), 2 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 378ed944b849..156832f01ebe 100644
@@ -138,6 +138,7 @@ KVM_X86_OP(complete_emulated_msr)
 KVM_X86_OP(vcpu_deliver_sipi_vector)
 KVM_X86_OP_OPTIONAL_RET0(vcpu_get_apicv_inhibit_reasons);
 KVM_X86_OP_OPTIONAL(get_untagged_addr)
+KVM_X86_OP_OPTIONAL_RET0(gmem_max_level)
 
 #undef KVM_X86_OP
 #undef KVM_X86_OP_OPTIONAL
arch/x86/include/asm/kvm_host.h +2 -0
index d271ba20a0b2..d15f5b4b1656 100644
@@ -1796,6 +1796,8 @@ struct kvm_x86_ops {
 	unsigned long (*vcpu_get_apicv_inhibit_reasons)(struct kvm_vcpu *vcpu);
 
 	gva_t (*get_untagged_addr)(struct kvm_vcpu *vcpu, gva_t gva, unsigned int flags);
+
+	int (*gmem_max_level)(struct kvm *kvm, kvm_pfn_t pfn, gfn_t gfn, u8 *max_level);
 };
 
 struct kvm_x86_nested_ops {
arch/x86/kvm/mmu/mmu.c +10 -2
index 2d6cdeab1f8a..1e5e12d2707d 100644
@@ -4308,6 +4308,7 @@ static int kvm_faultin_pfn_private(struct kvm_vcpu *vcpu,
 				   struct kvm_page_fault *fault)
 {
 	int max_order, r;
+	u8 max_level;
 
 	if (!kvm_slot_can_be_private(fault->slot)) {
 		kvm_mmu_prepare_memory_fault_exit(vcpu, fault);
@@ -4321,8 +4322,15 @@ static int kvm_faultin_pfn_private(struct kvm_vcpu *vcpu,
 		return r;
 	}
 
-	fault->max_level = min(kvm_max_level_for_order(max_order),
-			       fault->max_level);
+	max_level = kvm_max_level_for_order(max_order);
+	r = static_call(kvm_x86_gmem_max_level)(vcpu->kvm, fault->pfn,
+						fault->gfn, &max_level);
+	if (r) {
+		kvm_release_pfn_clean(fault->pfn);
+		return r;
+	}
+
+	fault->max_level = min(max_level, fault->max_level);
 	fault->map_writable = !(fault->slot->flags & KVM_MEM_READONLY);
 
 	return RET_PF_CONTINUE;
Signature · 1 line
2.25.1
#55 lore raw

PATCH v19 10/130 KVM: x86: Pass is_private to gmem hook of gmem_max_level

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TDX wants to know the faulting address is shared or private so that the max level is limited by Secure-EPT or not. Because fault->gfn doesn't include shared bit, gfn doesn't tell if the faulting address is shared or not. Pass is_private for TDX case.

TDX logic will be if (!is_private) return 0; else return PG_LEVEL_4K.

---
 arch/x86/include/asm/kvm_host.h | 3 ++-
 arch/x86/kvm/mmu/mmu.c          | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)
arch/x86/include/asm/kvm_host.h +2 -1
index d15f5b4b1656..57ce89fc2740 100644
@@ -1797,7 +1797,8 @@ struct kvm_x86_ops {
 
 	gva_t (*get_untagged_addr)(struct kvm_vcpu *vcpu, gva_t gva, unsigned int flags);
 
-	int (*gmem_max_level)(struct kvm *kvm, kvm_pfn_t pfn, gfn_t gfn, u8 *max_level);
+	int (*gmem_max_level)(struct kvm *kvm, kvm_pfn_t pfn, gfn_t gfn,
+			      bool is_private, u8 *max_level);
 };
 
 struct kvm_x86_nested_ops {
arch/x86/kvm/mmu/mmu.c +2 -1
index 1e5e12d2707d..22db1a9f528a 100644
@@ -4324,7 +4324,8 @@ static int kvm_faultin_pfn_private(struct kvm_vcpu *vcpu,
 
 	max_level = kvm_max_level_for_order(max_order);
 	r = static_call(kvm_x86_gmem_max_level)(vcpu->kvm, fault->pfn,
-						fault->gfn, &max_level);
+						fault->gfn, fault->is_private,
+						&max_level);
 	if (r) {
 		kvm_release_pfn_clean(fault->pfn);
 		return r;
Signature · 1 line
2.25.1
#56 lore raw

Re: [PATCH v19 010/130] KVM: x86: Pass is_private to gmem hook of gmem_max_level

Quoted 34 lines · On Mon, Feb 26, 2024 at 12:25:12AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX wants to know the faulting address is shared or private so that the max level is limited by Secure-EPT or not. Because fault->gfn doesn't include shared bit, gfn doesn't tell if the faulting address is shared or not. Pass is_private for TDX case.

TDX logic will be if (!is_private) return 0; else return PG_LEVEL_4K.

---
 arch/x86/include/asm/kvm_host.h | 3 ++-
 arch/x86/kvm/mmu/mmu.c          | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)
arch/x86/include/asm/kvm_host.h +2 -1
index d15f5b4b1656..57ce89fc2740 100644
@@ -1797,7 +1797,8 @@ struct kvm_x86_ops {
 
 	gva_t (*get_untagged_addr)(struct kvm_vcpu *vcpu, gva_t gva, unsigned int flags);
 
-	int (*gmem_max_level)(struct kvm *kvm, kvm_pfn_t pfn, gfn_t gfn, u8 *max_level);
+	int (*gmem_max_level)(struct kvm *kvm, kvm_pfn_t pfn, gfn_t gfn,
+			      bool is_private, u8 *max_level);
 };
 
 struct kvm_x86_nested_ops {
arch/x86/kvm/mmu/mmu.c +0 -0
index 1e5e12d2707d..22db1a9f528a 100644

arch/x86/kvm/mmu/mmu.c static int kvm_faultin_pfn_private(struct kvm_vcpu *vcpu, · line 4328

Quoted 6 lines
arch/x86/kvm/mmu/mmu.c +2 -1
@@ -4324,7 +4324,8 @@ static int kvm_faultin_pfn_private(struct kvm_vcpu *vcpu,
 
 	max_level = kvm_max_level_for_order(max_order);
 	r = static_call(kvm_x86_gmem_max_level)(vcpu->kvm, fault->pfn,
-						fault->gfn, &max_level);
+						fault->gfn, fault->is_private,
+						&max_level);

fault->is_private is always true in kvm_faultin_pfn_private(). Besides, as shared page allocation will not go to kvm_faultin_pfn_private(), why do we need to add the "is_private" parameter ?

Quoted 7 lines
arch/x86/kvm/mmu/mmu.c +0 -0
 	if (r) {
 		kvm_release_pfn_clean(fault->pfn);
 		return r;
Signature · 1 line
2.25.1
#57 lore raw

Re: [PATCH v19 010/130] KVM: x86: Pass is_private to gmem hook of gmem_max_level

Quoted 36 lines · Yan Zhao
Quoted 35 lines · On Mon, Feb 26, 2024 at 12:25:12AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX wants to know the faulting address is shared or private so that the max level is limited by Secure-EPT or not. Because fault->gfn doesn't include shared bit, gfn doesn't tell if the faulting address is shared or not. Pass is_private for TDX case.

TDX logic will be if (!is_private) return 0; else return PG_LEVEL_4K.

---
 arch/x86/include/asm/kvm_host.h | 3 ++-
 arch/x86/kvm/mmu/mmu.c          | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)
arch/x86/include/asm/kvm_host.h +2 -1
index d15f5b4b1656..57ce89fc2740 100644
@@ -1797,7 +1797,8 @@ struct kvm_x86_ops {
 
 	gva_t (*get_untagged_addr)(struct kvm_vcpu *vcpu, gva_t gva, unsigned int flags);
 
-	int (*gmem_max_level)(struct kvm *kvm, kvm_pfn_t pfn, gfn_t gfn, u8 *max_level);
+	int (*gmem_max_level)(struct kvm *kvm, kvm_pfn_t pfn, gfn_t gfn,
+			      bool is_private, u8 *max_level);
 };
 
 struct kvm_x86_nested_ops {
arch/x86/kvm/mmu/mmu.c +0 -0
index 1e5e12d2707d..22db1a9f528a 100644
@@ -4324,7 +4324,8 @@ static int kvm_faultin_pfn_private(struct kvm_vcpu *vcpu,
 
Quoted 8 lines

arch/x86/kvm/mmu/mmu.c static int kvm_faultin_pfn_private(struct kvm_vcpu *vcpu, · line 4328

Quoted 5 lines
arch/x86/kvm/mmu/mmu.c +2 -1
 	max_level = kvm_max_level_for_order(max_order);
 	r = static_call(kvm_x86_gmem_max_level)(vcpu->kvm, fault->pfn,
-						fault->gfn, &max_level);
+						fault->gfn, fault->is_private,
+						&max_level);

fault->is_private is always true in kvm_faultin_pfn_private(). Besides, as shared page allocation will not go to kvm_faultin_pfn_private(), why do we need to add the "is_private" parameter ?

You're right, we don't need this patch.
As Paolo picked the patch to add a hook, the discussion is happening at
https://lore.kernel.org/all/[email protected]/#t

Signature · 1 line
Isaku Yamahata <[email protected]>
#58 lore raw

PATCH v19 11/130 KVM: Add new members to struct kvm_gfn_range to operate on

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Add new members to strut kvm_gfn_range to indicate which mapping (private-vs-shared) to operate on. only_private and only_shared. Update mmu notifier, set memory attributes ioctl or KVM gmem callback to initialize them.

It was premature for set_memory_attributes ioctl to call kvm_unmap_gfn_range(). Instead, let kvm_arch_ste_memory_attributes() handle it and add a new x86 vendor callback to react to memory attribute change. [1]

- If it's from the mmu notifier, zap shared pages only
- If it's from the KVM gmem, zap private pages only
- If setting memory attributes, vendor callback checks new attributes and make decisions.
SNP would do nothing and handle it later with gmem callback TDX callback would do as follows.
When it converts pages to shared, zap private pages only. When it converts pages to private, zap shared pages only.

TDX needs to know which mapping to operate on. Shared-EPT vs. Secure-EPT. The following sequence to convert the GPA to private doesn't work for TDX because the page can already be private.

1) Update memory attributes to private in memory attributes xarray
2) Zap the GPA range irrespective of private-or-shared. Even if the page is already private, zap the entry.
3) EPT violation on the GPA
4) Populate the GPA as private
The page is zeroed, and the guest has to accept the page again.

In step 2, TDX wants to zap only shared pages and skip private ones.

[1] https://lore.kernel.org/all/[email protected]/

---
Changes v18:
- rebased to kvm-next

Changes v2 -> v3:
- Drop the KVM_GFN_RANGE flags
- Updated struct kvm_gfn_range
- Change kvm_arch_set_memory_attributes() to return bool for flush
- Added set_memory_attributes x86 op for vendor backends
- Refined commit message to describe TDX care concretely

Changes v1 -> v2:
- consolidate KVM_GFN_RANGE_FLAGS_GMEM_{PUNCH_HOLE, RELEASE} into KVM_GFN_RANGE_FLAGS_GMEM.
- Update the commit message to describe TDX more. Drop SEV_SNP.

---
 include/linux/kvm_host.h |  2 ++
 virt/kvm/guest_memfd.c   |  3 +++
 virt/kvm/kvm_main.c      | 17 +++++++++++++++++
 3 files changed, 22 insertions(+)
include/linux/kvm_host.h +2 -0
index 7e7fd25b09b3..0520cd8d03cc 100644
@@ -264,6 +264,8 @@ struct kvm_gfn_range {
 	gfn_t start;
 	gfn_t end;
 	union kvm_mmu_notifier_arg arg;
+	bool only_private;
+	bool only_shared;
 	bool may_block;
 };
 bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range);
virt/kvm/guest_memfd.c +3 -0
index 0f4e0cf4f158..3830d50b9b67 100644
@@ -64,6 +64,9 @@ static void kvm_gmem_invalidate_begin(struct kvm_gmem *gmem, pgoff_t start,
 			.end = slot->base_gfn + min(pgoff + slot->npages, end) - pgoff,
 			.slot = slot,
 			.may_block = true,
+			/* guest memfd is relevant to only private mappings. */
+			.only_private = true,
+			.only_shared = false,
 		};
 
 		if (!found_memslot) {
virt/kvm/kvm_main.c +17 -0
index 10bfc88a69f7..0349e1f241d1 100644
@@ -634,6 +634,12 @@ static __always_inline kvm_mn_ret_t __kvm_handle_hva_range(struct kvm *kvm,
 			 */
 			gfn_range.arg = range->arg;
 			gfn_range.may_block = range->may_block;
+			/*
+			 * HVA-based notifications aren't relevant to private
+			 * mappings as they don't have a userspace mapping.
+			 */
+			gfn_range.only_private = false;
+			gfn_range.only_shared = true;
 
 			/*
 			 * {gfn(page) | page intersects with [hva_start, hva_end)} =
@@ -2486,6 +2492,16 @@ static __always_inline void kvm_handle_gfn_range(struct kvm *kvm,
 	gfn_range.arg = range->arg;
 	gfn_range.may_block = range->may_block;
 
+	/*
+	 * If/when KVM supports more attributes beyond private .vs shared, this
+	 * _could_ set only_{private,shared} appropriately if the entire target
+	 * range already has the desired private vs. shared state (it's unclear
+	 * if that is a net win).  For now, KVM reaches this point if and only
+	 * if the private flag is being toggled, i.e. all mappings are in play.
+	 */
+	gfn_range.only_private = false;
+	gfn_range.only_shared = false;
+
 	for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
 		slots = __kvm_memslots(kvm, i);
 
@@ -2542,6 +2558,7 @@ static int kvm_vm_set_mem_attributes(struct kvm *kvm, gfn_t start, gfn_t end,
 	struct kvm_mmu_notifier_range pre_set_range = {
 		.start = start,
 		.end = end,
+		.arg.attributes = attributes,
 		.handler = kvm_pre_set_memory_attributes,
 		.on_lock = kvm_mmu_invalidate_begin,
 		.flush_on_ret = true,
Signature · 1 line
2.25.1
#59 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Quoted 9 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Add new members to strut kvm_gfn_range to indicate which mapping (private-vs-shared) to operate on. only_private and only_shared. Update mmu notifier, set memory attributes ioctl or KVM gmem callback to initialize them.

It was premature for set_memory_attributes ioctl to call kvm_unmap_gfn_range(). Instead, let kvm_arch_ste_memory_attributes()

"kvm_arch_ste_memory_attributes()" -> "kvm_vm_set_mem_attributes()" ?

Quoted 2 lines

handle it and add a new x86 vendor callback to react to memory attribute change. [1]

Which new x86 vendor callback?

Quoted 56 lines

- If it's from the mmu notifier, zap shared pages only
- If it's from the KVM gmem, zap private pages only
- If setting memory attributes, vendor callback checks new attributes and make decisions.
SNP would do nothing and handle it later with gmem callback TDX callback would do as follows.
When it converts pages to shared, zap private pages only. When it converts pages to private, zap shared pages only.

TDX needs to know which mapping to operate on. Shared-EPT vs. Secure-EPT. The following sequence to convert the GPA to private doesn't work for TDX because the page can already be private.

1) Update memory attributes to private in memory attributes xarray
2) Zap the GPA range irrespective of private-or-shared. Even if the page is already private, zap the entry.
3) EPT violation on the GPA
4) Populate the GPA as private
The page is zeroed, and the guest has to accept the page again.

In step 2, TDX wants to zap only shared pages and skip private ones.

[1] https://lore.kernel.org/all/[email protected]/

---
Changes v18:
- rebased to kvm-next

Changes v2 -> v3:
- Drop the KVM_GFN_RANGE flags
- Updated struct kvm_gfn_range
- Change kvm_arch_set_memory_attributes() to return bool for flush
- Added set_memory_attributes x86 op for vendor backends
- Refined commit message to describe TDX care concretely

Changes v1 -> v2:
- consolidate KVM_GFN_RANGE_FLAGS_GMEM_{PUNCH_HOLE, RELEASE} into KVM_GFN_RANGE_FLAGS_GMEM.
- Update the commit message to describe TDX more. Drop SEV_SNP.

---
  include/linux/kvm_host.h |  2 ++
  virt/kvm/guest_memfd.c   |  3 +++
  virt/kvm/kvm_main.c      | 17 +++++++++++++++++
  3 files changed, 22 insertions(+)
include/linux/kvm_host.h +0 -0
index 7e7fd25b09b3..0520cd8d03cc 100644

include/linux/kvm_host.h struct kvm_gfn_range { · line 268

Quoted 5 lines
include/linux/kvm_host.h +2 -0
@@ -264,6 +264,8 @@ struct kvm_gfn_range {
  	gfn_t start;
  	gfn_t end;
  	union kvm_mmu_notifier_arg arg;
+	bool only_private;
+	bool only_shared;

IMO, an enum will be clearer than the two flags.

    enum {
        PROCESS_PRIVATE_AND_SHARED,
        PROCESS_ONLY_PRIVATE,
        PROCESS_ONLY_SHARED,
    };

Quoted 59 lines
include/linux/kvm_host.h +0 -0
  	bool may_block;
  };
  bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range);
virt/kvm/guest_memfd.c +3 -0
index 0f4e0cf4f158..3830d50b9b67 100644
@@ -64,6 +64,9 @@ static void kvm_gmem_invalidate_begin(struct kvm_gmem *gmem, pgoff_t start,
  			.end = slot->base_gfn + min(pgoff + slot->npages, end) - pgoff,
  			.slot = slot,
  			.may_block = true,
+			/* guest memfd is relevant to only private mappings. */
+			.only_private = true,
+			.only_shared = false,
  		};
 
  		if (!found_memslot) {
virt/kvm/kvm_main.c +17 -0
index 10bfc88a69f7..0349e1f241d1 100644
@@ -634,6 +634,12 @@ static __always_inline kvm_mn_ret_t __kvm_handle_hva_range(struct kvm *kvm,
  			 */
  			gfn_range.arg = range->arg;
  			gfn_range.may_block = range->may_block;
+			/*
+			 * HVA-based notifications aren't relevant to private
+			 * mappings as they don't have a userspace mapping.
+			 */
+			gfn_range.only_private = false;
+			gfn_range.only_shared = true;
 
  			/*
  			 * {gfn(page) | page intersects with [hva_start, hva_end)} =
@@ -2486,6 +2492,16 @@ static __always_inline void kvm_handle_gfn_range(struct kvm *kvm,
  	gfn_range.arg = range->arg;
  	gfn_range.may_block = range->may_block;
 
+	/*
+	 * If/when KVM supports more attributes beyond private .vs shared, this
+	 * _could_ set only_{private,shared} appropriately if the entire target
+	 * range already has the desired private vs. shared state (it's unclear
+	 * if that is a net win).  For now, KVM reaches this point if and only
+	 * if the private flag is being toggled, i.e. all mappings are in play.
+	 */
+	gfn_range.only_private = false;
+	gfn_range.only_shared = false;
+
  	for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
  		slots = __kvm_memslots(kvm, i);
 
@@ -2542,6 +2558,7 @@ static int kvm_vm_set_mem_attributes(struct kvm *kvm, gfn_t start, gfn_t end,
  	struct kvm_mmu_notifier_range pre_set_range = {
  		.start = start,
  		.end = end,
+		.arg.attributes = attributes,
  		.handler = kvm_pre_set_memory_attributes,
  		.on_lock = kvm_mmu_invalidate_begin,
  		.flush_on_ret = true,
#60 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Quoted 5 lines · Binbin Wu
Quoted 2 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Quoted 8 lines
Quoted 7 lines

Add new members to strut kvm_gfn_range to indicate which mapping (private-vs-shared) to operate on. only_private and only_shared. Update mmu notifier, set memory attributes ioctl or KVM gmem callback to initialize them.

It was premature for set_memory_attributes ioctl to call kvm_unmap_gfn_range(). Instead, let kvm_arch_ste_memory_attributes()

"kvm_arch_ste_memory_attributes()" -> "kvm_vm_set_mem_attributes()" ?

Yes, will fix it.

Quoted 3 lines
Quoted 2 lines

handle it and add a new x86 vendor callback to react to memory attribute change. [1]

Which new x86 vendor callback?

Now we don't have it. Will drop this sentnse.

Quoted 13 lines

include/linux/kvm_host.h struct kvm_gfn_range { · line 268

Quoted 10 lines
include/linux/kvm_host.h +2 -0
index 7e7fd25b09b3..0520cd8d03cc 100644
@@ -264,6 +264,8 @@ struct kvm_gfn_range {
  	gfn_t start;
  	gfn_t end;
  	union kvm_mmu_notifier_arg arg;
+	bool only_private;
+	bool only_shared;

IMO, an enum will be clearer than the two flags.

Quoted 5 lines

    enum {
        PROCESS_PRIVATE_AND_SHARED,
        PROCESS_ONLY_PRIVATE,
        PROCESS_ONLY_SHARED,
    };

The code will be ugly like
"if (== PRIVATE || == PRIVATE_AND_SHARED)" or
"if (== SHARED || == PRIVATE_AND_SHARED)"

two boolean (or two flags) is less error-prone.

Thanks,

Signature · 1 line
Isaku Yamahata <[email protected]>
#61 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Quoted 8 lines · Isaku Yamahata
Quoted 7 lines

IMO, an enum will be clearer than the two flags.

    enum {
        PROCESS_PRIVATE_AND_SHARED,
        PROCESS_ONLY_PRIVATE,
        PROCESS_ONLY_SHARED,
    };

Quoted 5 lines

The code will be ugly like
"if (== PRIVATE || == PRIVATE_AND_SHARED)" or
"if (== SHARED || == PRIVATE_AND_SHARED)"

two boolean (or two flags) is less error-prone.

Yes the enum would be awkward to handle. But I also thought the way this is specified in struct kvm_gfn_range is a little strange.

It is ambiguous what it should mean if you set:
.only_private=true;
.only_shared=true;
...as happens later in the series (although it may be a mistake).

Reading the original conversation, it seems Sean suggested this specifically. But it wasn't clear to me from the discussion what the intention of the "only" semantics was. Like why not?
bool private;
bool shared;

#62 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Quoted 23 lines · Rick Edgecombe
Quoted 8 lines · Isaku Yamahata
Quoted 7 lines

IMO, an enum will be clearer than the two flags.

    enum {
        PROCESS_PRIVATE_AND_SHARED,
        PROCESS_ONLY_PRIVATE,
        PROCESS_ONLY_SHARED,
    };

Quoted 5 lines

The code will be ugly like
"if (== PRIVATE || == PRIVATE_AND_SHARED)" or
"if (== SHARED || == PRIVATE_AND_SHARED)"

two boolean (or two flags) is less error-prone.

Yes the enum would be awkward to handle. But I also thought the way this is specified in struct kvm_gfn_range is a little strange.

It is ambiguous what it should mean if you set:
 .only_private=true;
 .only_shared=true;
...as happens later in the series (although it may be a mistake).

Quoted 5 lines

Reading the original conversation, it seems Sean suggested this specifically. But it wasn't clear to me from the discussion what the intention of the "only" semantics was. Like why not?
 bool private;
 bool shared;

I see Binbin brought up this point on v18 as well:
https://lore.kernel.org/kvm/[email protected]/#t

and helpfully dug up some other discussion with Sean where he agreed the "_only" is confusing and proposed the the enum:
https://lore.kernel.org/kvm/[email protected]/

He wanted the default value (in the case the caller forgets to set them), to be to include both private and shared. I think the enum has the issues that Isaku mentioned. What about?

bool exclude_private;
bool exclude_shared;

It will become onerous if more types of aliases grow, but it clearer semantically and has the safe default behavior.

#63 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Quoted 41 lines · Rick P
Quoted 23 lines · Rick Edgecombe
Quoted 8 lines · Isaku Yamahata
Quoted 7 lines

IMO, an enum will be clearer than the two flags.

    enum {
        PROCESS_PRIVATE_AND_SHARED,
        PROCESS_ONLY_PRIVATE,
        PROCESS_ONLY_SHARED,
    };

Quoted 5 lines

The code will be ugly like
"if (== PRIVATE || == PRIVATE_AND_SHARED)" or
"if (== SHARED || == PRIVATE_AND_SHARED)"

two boolean (or two flags) is less error-prone.

Yes the enum would be awkward to handle. But I also thought the way this is specified in struct kvm_gfn_range is a little strange.

It is ambiguous what it should mean if you set:
 .only_private=true;
 .only_shared=true;
...as happens later in the series (although it may be a mistake).

Quoted 5 lines

Reading the original conversation, it seems Sean suggested this specifically. But it wasn't clear to me from the discussion what the intention of the "only" semantics was. Like why not?
 bool private;
 bool shared;

I see Binbin brought up this point on v18 as well:
https://lore.kernel.org/kvm/[email protected]/#t

and helpfully dug up some other discussion with Sean where he agreed the "_only" is confusing and proposed the the enum:
https://lore.kernel.org/kvm/[email protected]/

He wanted the default value (in the case the caller forgets to set them), to be to include both private and shared. I think the enum has the issues that Isaku mentioned. What about?

Quoted 5 lines

bool exclude_private;
bool exclude_shared;

It will become onerous if more types of aliases grow, but it clearer semantically and has the safe default behavior.

I'm fine with those names. Anyway, I'm fine with wither way, two bools or enum.

Signature · 1 line
Isaku Yamahata <[email protected]>
#64 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Hi,

Quoted 45 lines · Isaku Yamahata
Quoted 41 lines · Rick P
Quoted 23 lines · Rick Edgecombe
Quoted 8 lines · Isaku Yamahata
Quoted 7 lines

IMO, an enum will be clearer than the two flags.

    enum {
        PROCESS_PRIVATE_AND_SHARED,
        PROCESS_ONLY_PRIVATE,
        PROCESS_ONLY_SHARED,
    };
Quoted 5 lines

The code will be ugly like
"if (== PRIVATE || == PRIVATE_AND_SHARED)" or
"if (== SHARED || == PRIVATE_AND_SHARED)"

two boolean (or two flags) is less error-prone.

Yes the enum would be awkward to handle. But I also thought the way this is specified in struct kvm_gfn_range is a little strange.

It is ambiguous what it should mean if you set:
.only_private=true;
.only_shared=true;
...as happens later in the series (although it may be a mistake).

Quoted 5 lines

Reading the original conversation, it seems Sean suggested this specifically. But it wasn't clear to me from the discussion what the intention of the "only" semantics was. Like why not?
bool private;
bool shared;

I see Binbin brought up this point on v18 as well:
https://lore.kernel.org/kvm/[email protected]/#t

and helpfully dug up some other discussion with Sean where he agreed the "_only" is confusing and proposed the the enum:
https://lore.kernel.org/kvm/[email protected]/

He wanted the default value (in the case the caller forgets to set them), to be to include both private and shared. I think the enum has the issues that Isaku mentioned. What about?

Quoted 7 lines
Quoted 5 lines

bool exclude_private;
bool exclude_shared;

It will become onerous if more types of aliases grow, but it clearer semantically and has the safe default behavior.

I'm fine with those names. Anyway, I'm fine with wither way, two bools or enum.

I don't have a strong opinion, but I'd brought it up in a previous patch series. I think that having two bools to encode three states is less intuitive and potentially more bug prone, more so than the naming itself (i.e., _only):
https://lore.kernel.org/all/[email protected]/

Cheers,
/fuad

Quoted 3 lines

--
Isaku Yamahata <[email protected]>

#65 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Quoted 8 lines · Fuad Tabba
Quoted 2 lines

I'm fine with those names. Anyway, I'm fine with wither way, two bools or enum.

I don't have a strong opinion, but I'd brought it up in a previous patch series. I think that having two bools to encode three states is less intuitive and potentially more bug prone, more so than the naming itself (i.e., _only):
https://lore.kernel.org/all/[email protected]/

Currently in our internal branch we switched to:
exclude_private
exclude_shared

It came together bettter in the code that uses it.

But I started to wonder if we actually really need exclude_shared. For TDX zapping private memory has to be done with more care, because it cannot be re- populated without guest coordination. But for shared memory if we are zapping a range that includes both private and shared memory, I don't think it should hurt to zap the shared memory.

#66 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Quoted 8 lines · Rick P Edgecombe
Quoted 7 lines · Fuad Tabba
Quoted 2 lines

I'm fine with those names. Anyway, I'm fine with wither way, two bools or enum.

I don't have a strong opinion, but I'd brought it up in a previous patch series. I think that having two bools to encode three states is less intuitive and potentially more bug prone, more so than the naming itself (i.e., _only):

Hmm, yeah, I buy that argument. We could even harded further by poisoning '0' to force KVM to explicitly. Aha! And maybe use a bitmap?

	enum {
		BUGGY_KVM_INVALIDATION		= 0,
		PROCESS_SHARED			= BIT(0),
		PROCESS_PRIVATE			= BIT(1),
		PROCESS_PRIVATE_AND_SHARED	= PROCESS_SHARED | PROCESS_PRIVATE,
	};
Quoted 7 lines
Quoted 1 line

https://lore.kernel.org/all/[email protected]/

Currently in our internal branch we switched to:
exclude_private
exclude_shared

It came together bettter in the code that uses it.

If the choice is between an enum and exclude_*, I would strongly prefer the enum. Using exclude_* results in inverted polarity for the code that triggers invalidations.

Quoted 5 lines

But I started to wonder if we actually really need exclude_shared. For TDX zapping private memory has to be done with more care, because it cannot be re- populated without guest coordination. But for shared memory if we are zapping a range that includes both private and shared memory, I don't think it should hurt to zap the shared memory.

Hell no, I am not risking taking on more baggage in KVM where userspace or some other subsystem comes to rely on KVM spuriously zapping SPTEs in response to an unrelated userspace action.

#67 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Quoted 10 lines · Sean Christopherson

Hmm, yeah, I buy that argument.  We could even harded further by poisoning '0' to force KVM to explicitly.  Aha!  And maybe use a bitmap?

        enum {
                BUGGY_KVM_INVALIDATION          = 0, PROCESS_SHARED                  = BIT(0), PROCESS_PRIVATE                 = BIT(1), PROCESS_PRIVATE_AND_SHARED      = PROCESS_SHARED | PROCESS_PRIVATE,
        };

Seems like it would work for all who have been concerned. The previous objection to the enum (can't find the mail) was for requiring logic like:

   if (zap == PROCESS_PRIVATE_AND_SHARED || zap == PROCESS_PRIVATE)
   	do_private_zap_stuff();

We are trying to tie things up internally so we can jointly have something to stare at again, as the patches are diverging. But will make this adjustment.

Quoted 7 lines
Quoted 7 lines
Quoted 2 lines

https://lore.kernel.org/all/[email protected]/

Currently in our internal branch we switched to:
exclude_private
exclude_shared

Quoted 6 lines
Quoted 1 line

It came together bettter in the code that uses it.

If the choice is between an enum and exclude_*, I would strongly prefer the enum.
Using exclude_* results in inverted polarity for the code that triggers invalidations.

Right, the awkwardness lands in that code.

The processing code looks nice though:
https://lore.kernel.org/kvm/[email protected]/

Quoted 10 lines
Quoted 9 lines

But I started to wonder if we actually really need exclude_shared. For TDX zapping private memory has to be done with more care, because it cannot be re-
populated without guest coordination. But for shared memory if we are zapping a
range that includes both private and shared memory, I don't think it should hurt
to zap the shared memory.

Quoted 5 lines

Hell no, I am not risking taking on more baggage in KVM where userspace or some
other subsystem comes to rely on KVM spuriously zapping SPTEs in response to an
unrelated userspace action.

Hmm, I see the point. Thanks. This was just being left for later discussion anyway.

#68 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Quoted 9 lines · Rick P Edgecombe
Quoted 3 lines · Sean Christopherson

If the choice is between an enum and exclude_*, I would strongly prefer the enum. Using exclude_* results in inverted polarity for the code that triggers invalidations.

Right, the awkwardness lands in that code.

The processing code looks nice though:
https://lore.kernel.org/kvm/[email protected]/

Heh, where's your bitmask abuse spirit? It's a little evil (and by "evil" I mean awesome), but the need to process different roots is another good argument for an enum+bitmask.

enum tdp_mmu_root_types {
KVM_SHARED_ROOTS = KVM_PROCESS_SHARED,
KVM_PRIVATE_ROOTS = KVM_PROCESS_PRIVATE,
KVM_VALID_ROOTS = BIT(2),
KVM_ANY_VALID_ROOT = KVM_SHARED_ROOT | KVM_PRIVATE_ROOT | KVM_VALID_ROOT, KVM_ANY_ROOT = KVM_SHARED_ROOT | KVM_PRIVATE_ROOT,
}
static_assert(!(KVM_SHARED_ROOTS & KVM_VALID_ROOTS)); static_assert(!(KVM_PRIVATE_ROOTS & KVM_VALID_ROOTS)); static_assert(KVM_PRIVATE_ROOTS == (KVM_SHARED_ROOTS << 1));

/*
* Returns the next root after @prev_root (or the first root if @prev_root is
* NULL). A reference to the returned root is acquired, and the reference to
* @prev_root is released (the caller obviously must hold a reference to
* @prev_root if it's non-NULL).
*
* Returns NULL if the end of tdp_mmu_roots was reached.
*/
static struct kvm_mmu_page *tdp_mmu_next_root(struct kvm *kvm,
struct kvm_mmu_page *prev_root, enum tdp_mmu_root_types types)

{
	bool only_valid = types & KVM_VALID_ROOTS;
	struct kvm_mmu_page *next_root;

	/*
	 * While the roots themselves are RCU-protected, fields such as
	 * role.invalid are protected by mmu_lock.
	 */
	lockdep_assert_held(&kvm->mmu_lock);

	rcu_read_lock();

	if (prev_root)
		next_root = list_next_or_null_rcu(&kvm->arch.tdp_mmu_roots,
						  &prev_root->link,
						  typeof(*prev_root), link);
	else
		next_root = list_first_or_null_rcu(&kvm->arch.tdp_mmu_roots,
						   typeof(*next_root), link);

	while (next_root) {
		if ((!only_valid || !next_root->role.invalid) &&
		    (types & (KVM_SHARED_ROOTS << is_private_sp(root))) &&
		    kvm_tdp_mmu_get_root(next_root))
			break;

		next_root = list_next_or_null_rcu(&kvm->arch.tdp_mmu_roots,
				&next_root->link, typeof(*next_root), link);
	}

	rcu_read_unlock();

	if (prev_root)
		kvm_tdp_mmu_put_root(kvm, prev_root);

	return next_root;
}
#define __for_each_tdp_mmu_root_yield_safe(_kvm, _root, _as_id, _types)		\
	for (_root = tdp_mmu_next_root(_kvm, NULL, _types);			\
	     ({ lockdep_assert_held(&(_kvm)->mmu_lock); }), _root;		\
	     _root = tdp_mmu_next_root(_kvm, _root, _types))			\
		if (_as_id >= 0 && kvm_mmu_page_as_id(_root) != _as_id) {	\
		} else
#define for_each_valid_tdp_mmu_root_yield_safe(_kvm, _root, _as_id)		\
	__for_each_tdp_mmu_root_yield_safe(_kvm, _root, _as_id, KVM_ANY_VALID_ROOT)
#define for_each_tdp_mmu_root_yield_safe(_kvm, _root)				\
	for (_root = tdp_mmu_next_root(_kvm, NULL, KVM_ANY_ROOT);		\
	     ({ lockdep_assert_held(&(_kvm)->mmu_lock); }), _root;		\
	     _root = tdp_mmu_next_root(_kvm, _root, KVM_ANY_ROOT))
#69 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Quoted 6 lines · Sean Christopherson

Heh, where's your bitmask abuse spirit?  It's a little evil (and by "evil" I mean
awesome), but the need to process different roots is another good argument for an
enum+bitmask.

Haha. There seems to be some special love for bit math in KVM. I was just relaying my struggle to understand permission_fault() the other day.

Quoted 79 lines

enum tdp_mmu_root_types {
        KVM_SHARED_ROOTS = KVM_PROCESS_SHARED, KVM_PRIVATE_ROOTS = KVM_PROCESS_PRIVATE, KVM_VALID_ROOTS = BIT(2),
        KVM_ANY_VALID_ROOT = KVM_SHARED_ROOT | KVM_PRIVATE_ROOT | KVM_VALID_ROOT,
        KVM_ANY_ROOT = KVM_SHARED_ROOT | KVM_PRIVATE_ROOT, }
static_assert(!(KVM_SHARED_ROOTS & KVM_VALID_ROOTS)); static_assert(!(KVM_PRIVATE_ROOTS & KVM_VALID_ROOTS)); static_assert(KVM_PRIVATE_ROOTS == (KVM_SHARED_ROOTS << 1));

/*
 * Returns the next root after @prev_root (or the first root if @prev_root is
 * NULL).  A reference to the returned root is acquired, and the reference to
 * @prev_root is released (the caller obviously must hold a reference to
 * @prev_root if it's non-NULL).
 *
 * Returns NULL if the end of tdp_mmu_roots was reached.
 */
static struct kvm_mmu_page *tdp_mmu_next_root(struct kvm *kvm, struct kvm_mmu_page *prev_root, enum tdp_mmu_root_types types) {
        bool only_valid = types & KVM_VALID_ROOTS;
        struct kvm_mmu_page *next_root;

        /*
         * While the roots themselves are RCU-protected, fields such as
         * role.invalid are protected by mmu_lock.
         */
        lockdep_assert_held(&kvm->mmu_lock);

        rcu_read_lock();

        if (prev_root)
                next_root = list_next_or_null_rcu(&kvm->arch.tdp_mmu_roots, &prev_root->link, typeof(*prev_root), link); else
                next_root = list_first_or_null_rcu(&kvm->arch.tdp_mmu_roots, typeof(*next_root), link);

        while (next_root) {
                if ((!only_valid || !next_root->role.invalid) && (types & (KVM_SHARED_ROOTS << is_private_sp(root))) && kvm_tdp_mmu_get_root(next_root))
                        break;

                next_root = list_next_or_null_rcu(&kvm->arch.tdp_mmu_roots, &next_root->link, typeof(*next_root), link); }

        rcu_read_unlock();

        if (prev_root) kvm_tdp_mmu_put_root(kvm, prev_root);

        return next_root;
}

#define __for_each_tdp_mmu_root_yield_safe(_kvm, _root, _as_id, _types)         \
        for (_root = tdp_mmu_next_root(_kvm, NULL,
_types);                     \
             ({ lockdep_assert_held(&(_kvm)->mmu_lock); }), _root;              \
             _root = tdp_mmu_next_root(_kvm, _root,
_types))                    \
                if (_as_id >= 0 && kvm_mmu_page_as_id(_root) != _as_id) {       \
                } else

#define for_each_valid_tdp_mmu_root_yield_safe(_kvm, _root, _as_id)             \
        __for_each_tdp_mmu_root_yield_safe(_kvm, _root, _as_id, KVM_ANY_VALID_ROOT)

Quoted 7 lines

#define for_each_tdp_mmu_root_yield_safe(_kvm,
_root)                           \
        for (_root = tdp_mmu_next_root(_kvm, NULL, KVM_ANY_ROOT);               \
             ({ lockdep_assert_held(&(_kvm)->mmu_lock); }), _root;              \
             _root = tdp_mmu_next_root(_kvm, _root, KVM_ANY_ROOT))

Ohh, yes move it into the iterators. I like it a lot.

#70 lore raw

Re: [PATCH v19 011/130] KVM: Add new members to struct kvm_gfn_range to operate on

Quoted 8 lines · Rick P Edgecombe
Quoted 4 lines · Sean Christopherson

Heh, where's your bitmask abuse spirit?  It's a little evil (and by "evil" I mean awesome), but the need to process different roots is another good argument for an enum+bitmask.

Haha. There seems to be some special love for bit math in KVM. I was just relaying my struggle to understand permission_fault() the other day.

LOL, you and everyone else that's ever looked at that code. Just wait until you run into one of Paolo's decimal-based bitmasks.

#71 lore raw

PATCH v19 12/130 KVM: x86/mmu: Pass around full 64-bit error code for the KVM page fault

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Because the full 64-bit error code, or more info about the fault, for the KVM page fault will be needed for protected VM, TDX and SEV-SNP, update kvm_mmu_do_page_fault() to accept the 64-bit value so it can pass it to the callbacks.

The upper 32 bits of error code are discarded at kvm_mmu_page_fault() by lower_32_bits(). Now it's passed down as full 64 bits. Currently two hardware defined bits, PFERR_GUEST_FINAL_MASK and PFERR_GUEST_PAGE_MASK, and one software defined bit, PFERR_IMPLICIT_ACCESS, is defined.

PFERR_IMPLICIT_ACCESS:
commit 4f4aa80e3b88 ("KVM: X86: Handle implicit supervisor access with SMAP") introduced a software defined bit PFERR_IMPLICIT_ACCESS at bit 48 to indicate implicit access for SMAP with instruction emulator. Concretely emulator_read_std() and emulator_write_std() set the bit. permission_fault() checks the bit as smap implicit access. The vendor page fault handler shouldn't pass the bit to kvm_mmu_page_fault().

PFERR_GUEST_FINAL_MASK and PFERR_GUEST_PAGE_MASK:
commit 147277540bbc ("kvm: svm: Add support for additional SVM NPF error codes") introduced them to optimize the nested page fault handling. Other code path doesn't use the bits. Those two bits can be safely passed down without functionality change.

The accesses of fault->error_code are as follows
- FNAME(page_fault): PFERR_IMPLICIT_ACCESS shouldn't be passed down. PFERR_GUEST_FINAL_MASK and PFERR_GUEST_PAGE_MASK aren't used.
- kvm_mmu_page_fault(): explicit mask with PFERR_RSVD_MASK, and PFERR_NESTED_GUEST_PAGE is used outside of the masking upper 32 bits.
- mmutrace: change u32 -> u64

No functional change is intended. This is a preparation to pass on more info with page fault error code.

---
Changes v3 -> v4:
- Dropped debug print part as it was deleted in the kvm-x86-next

Changes v2 -> v3:
- Make depends on a patch to clear PFERR_IMPLICIT_ACCESS
- drop clearing the upper 32 bit, instead just pass whole 64 bits
- update commit message to mention about PFERR_IMPLICIT_ACCESS and PFERR_NESTED_GUEST_PAGE

Changes v1 -> v2:
- no change

---
 arch/x86/kvm/mmu/mmu.c          | 3 +--
 arch/x86/kvm/mmu/mmu_internal.h | 4 ++--
 arch/x86/kvm/mmu/mmutrace.h     | 2 +-
 3 files changed, 4 insertions(+), 5 deletions(-)
arch/x86/kvm/mmu/mmu.c +1 -2
index 22db1a9f528a..ccdbff3d85ec 100644
@@ -5822,8 +5822,7 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err
 	}
 
 	if (r == RET_PF_INVALID) {
-		r = kvm_mmu_do_page_fault(vcpu, cr2_or_gpa,
-					  lower_32_bits(error_code), false,
+		r = kvm_mmu_do_page_fault(vcpu, cr2_or_gpa, error_code, false,
 					  &emulation_type);
 		if (KVM_BUG_ON(r == RET_PF_INVALID, vcpu->kvm))
 			return -EIO;
arch/x86/kvm/mmu/mmu_internal.h +2 -2
index 0669a8a668ca..21f55e8b4dc6 100644
@@ -190,7 +190,7 @@ static inline bool is_nx_huge_page_enabled(struct kvm *kvm)
 struct kvm_page_fault {
 	/* arguments to kvm_mmu_do_page_fault.  */
 	const gpa_t addr;
-	const u32 error_code;
+	const u64 error_code;
 	const bool prefetch;
 
 	/* Derived from error_code.  */
@@ -280,7 +280,7 @@ enum {
 };
 
 static inline int kvm_mmu_do_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
-					u32 err, bool prefetch, int *emulation_type)
+					u64 err, bool prefetch, int *emulation_type)
 {
 	struct kvm_page_fault fault = {
 		.addr = cr2_or_gpa,
arch/x86/kvm/mmu/mmutrace.h +1 -1
index ae86820cef69..195d98bc8de8 100644
@@ -260,7 +260,7 @@ TRACE_EVENT(
 	TP_STRUCT__entry(
 		__field(int, vcpu_id)
 		__field(gpa_t, cr2_or_gpa)
-		__field(u32, error_code)
+		__field(u64, error_code)
 		__field(u64 *, sptep)
 		__field(u64, old_spte)
 		__field(u64, new_spte)
Signature · 1 line
2.25.1
#72 lore raw

PATCH v19 13/130 KVM: x86: Use PFERR_GUEST_ENC_MASK to indicate fault is private

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

SEV-SNP defines PFERR_GUEST_ENC_MASK (bit 32) in page-fault error bits to represent the guest page is encrypted. Use the bit to designate that the page fault is private and that it requires looking up memory attributes. The vendor kvm page fault handler should set PFERR_GUEST_ENC_MASK bit based on their fault information. It may or may not use the hardware value directly or parse the hardware value to set the bit.

For KVM_X86_SW_PROTECTED_VM, ask memory attributes for the fault privateness. For async page fault, carry the bit and use it for kvm page fault handler.

---
Changes v4 -> v5:
- Eliminate kvm_is_fault_private() by open code the function
- Make async page fault handler to carry is_private bit

Changes v3 -> v4:
- rename back struct kvm_page_fault::private => is_private
- catch up rename: KVM_X86_PROTECTED_VM => KVM_X86_SW_PROTECTED_VM

Changes v2 -> v3:
- Revive PFERR_GUEST_ENC_MASK
- rename struct kvm_page_fault::is_private => private
- Add check KVM_X86_PROTECTED_VM

Changes v1 -> v2:
- Introduced fault type and replaced is_private with fault_type.
- Add kvm_get_fault_type() to encapsulate the difference.

---
 arch/x86/include/asm/kvm_host.h |  3 +++
 arch/x86/kvm/mmu/mmu.c          | 24 +++++++++++++++++-------
 arch/x86/kvm/mmu/mmu_internal.h |  2 +-
 3 files changed, 21 insertions(+), 8 deletions(-)
arch/x86/include/asm/kvm_host.h +3 -0
index 57ce89fc2740..28314e7d546c 100644
@@ -264,6 +264,7 @@ enum x86_intercept_stage;
 #define PFERR_SGX_BIT 15
 #define PFERR_GUEST_FINAL_BIT 32
 #define PFERR_GUEST_PAGE_BIT 33
+#define PFERR_GUEST_ENC_BIT 34
 #define PFERR_IMPLICIT_ACCESS_BIT 48
 
 #define PFERR_PRESENT_MASK	BIT(PFERR_PRESENT_BIT)
@@ -275,6 +276,7 @@ enum x86_intercept_stage;
 #define PFERR_SGX_MASK		BIT(PFERR_SGX_BIT)
 #define PFERR_GUEST_FINAL_MASK	BIT_ULL(PFERR_GUEST_FINAL_BIT)
 #define PFERR_GUEST_PAGE_MASK	BIT_ULL(PFERR_GUEST_PAGE_BIT)
+#define PFERR_GUEST_ENC_MASK	BIT_ULL(PFERR_GUEST_ENC_BIT)
 #define PFERR_IMPLICIT_ACCESS	BIT_ULL(PFERR_IMPLICIT_ACCESS_BIT)
 
 #define PFERR_NESTED_GUEST_PAGE (PFERR_GUEST_PAGE_MASK |	\
@@ -1836,6 +1838,7 @@ struct kvm_arch_async_pf {
 	gfn_t gfn;
 	unsigned long cr3;
 	bool direct_map;
+	u64 error_code;
 };
 
 extern u32 __read_mostly kvm_nr_uret_msrs;
arch/x86/kvm/mmu/mmu.c +17 -7
index ccdbff3d85ec..61674d6b17aa 100644
@@ -4246,18 +4246,19 @@ static u32 alloc_apf_token(struct kvm_vcpu *vcpu)
 	return (vcpu->arch.apf.id++ << 12) | vcpu->vcpu_id;
 }
 
-static bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
-				    gfn_t gfn)
+static bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu,
+				    struct kvm_page_fault *fault)
 {
 	struct kvm_arch_async_pf arch;
 
 	arch.token = alloc_apf_token(vcpu);
-	arch.gfn = gfn;
+	arch.gfn = fault->gfn;
 	arch.direct_map = vcpu->arch.mmu->root_role.direct;
 	arch.cr3 = kvm_mmu_get_guest_pgd(vcpu, vcpu->arch.mmu);
+	arch.error_code = fault->error_code & PFERR_GUEST_ENC_MASK;
 
-	return kvm_setup_async_pf(vcpu, cr2_or_gpa,
-				  kvm_vcpu_gfn_to_hva(vcpu, gfn), &arch);
+	return kvm_setup_async_pf(vcpu, fault->addr,
+				  kvm_vcpu_gfn_to_hva(vcpu, fault->gfn), &arch);
 }
 
 void kvm_arch_async_page_ready(struct kvm_vcpu *vcpu, struct kvm_async_pf *work)
@@ -4276,7 +4277,8 @@ void kvm_arch_async_page_ready(struct kvm_vcpu *vcpu, struct kvm_async_pf *work)
 	      work->arch.cr3 != kvm_mmu_get_guest_pgd(vcpu, vcpu->arch.mmu))
 		return;
 
-	kvm_mmu_do_page_fault(vcpu, work->cr2_or_gpa, 0, true, NULL);
+	kvm_mmu_do_page_fault(vcpu, work->cr2_or_gpa, work->arch.error_code,
+			      true, NULL);
 }
 
 static inline u8 kvm_max_level_for_order(int order)
@@ -4390,7 +4392,7 @@ static int __kvm_faultin_pfn(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
 			trace_kvm_async_pf_repeated_fault(fault->addr, fault->gfn);
 			kvm_make_request(KVM_REQ_APF_HALT, vcpu);
 			return RET_PF_RETRY;
-		} else if (kvm_arch_setup_async_pf(vcpu, fault->addr, fault->gfn)) {
+		} else if (kvm_arch_setup_async_pf(vcpu, fault)) {
 			return RET_PF_RETRY;
 		}
 	}
@@ -5814,6 +5816,14 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err
 	if (WARN_ON_ONCE(!VALID_PAGE(vcpu->arch.mmu->root.hpa)))
 		return RET_PF_RETRY;
 
+	/*
+	 * This is racy with updating memory attributes with mmu_seq.  If we
+	 * hit a race, it would result in retrying page fault.
+	 */
+	if (vcpu->kvm->arch.vm_type == KVM_X86_SW_PROTECTED_VM &&
+	    kvm_mem_is_private(vcpu->kvm, gpa_to_gfn(cr2_or_gpa)))
+		error_code |= PFERR_GUEST_ENC_MASK;
+
 	r = RET_PF_INVALID;
 	if (unlikely(error_code & PFERR_RSVD_MASK)) {
 		r = handle_mmio_page_fault(vcpu, cr2_or_gpa, direct);
arch/x86/kvm/mmu/mmu_internal.h +1 -1
index 21f55e8b4dc6..0443bfcf5d9c 100644
@@ -292,13 +292,13 @@ static inline int kvm_mmu_do_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
 		.user = err & PFERR_USER_MASK,
 		.prefetch = prefetch,
 		.is_tdp = likely(vcpu->arch.mmu->page_fault == kvm_tdp_page_fault),
+		.is_private = err & PFERR_GUEST_ENC_MASK,
 		.nx_huge_page_workaround_enabled =
 			is_nx_huge_page_enabled(vcpu->kvm),
 
 		.max_level = KVM_MAX_HUGEPAGE_LEVEL,
 		.req_level = PG_LEVEL_4K,
 		.goal_level = PG_LEVEL_4K,
-		.is_private = kvm_mem_is_private(vcpu->kvm, cr2_or_gpa >> PAGE_SHIFT),
 	};
 	int r;
 
Signature · 1 line
2.25.1
#73 lore raw

Re: [PATCH v19 013/130] KVM: x86: Use PFERR_GUEST_ENC_MASK to indicate fault is private

arch/x86/kvm/mmu/mmu.c int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err · line 5823

Quoted 5 lines · On 2/26/24 16:25, [email protected] wrote:
arch/x86/kvm/mmu/mmu.c +5 -0
+	/*
+	 * This is racy with updating memory attributes with mmu_seq.  If we
+	 * hit a race, it would result in retrying page fault.
+	 */
+	if (vcpu->kvm->arch.vm_type == KVM_X86_SW_PROTECTED_VM &&

There are more than two times of similar check in this patchset. Maybe it's better to add a helper function to tell whether the type is KVM_X86_SW_PROTECTED_VM?

Regards
Yin, Fengwei

Quoted 3 lines
arch/x86/kvm/mmu/mmu.c +3 -0
+	    kvm_mem_is_private(vcpu->kvm, gpa_to_gfn(cr2_or_gpa)))
+		error_code |= PFERR_GUEST_ENC_MASK;
+
#74 lore raw

PATCH v19 14/130 KVM: Add KVM vcpu ioctl to pre-populate guest memory

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Add new ioctl KVM_MEMORY_MAPPING in the kvm common code. It iterates on the memory range and call arch specific function. Add stub function as weak symbol.

---
v19:
- newly added

---
 include/linux/kvm_host.h |  4 +++
 include/uapi/linux/kvm.h | 10 ++++++
 virt/kvm/kvm_main.c      | 67 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 81 insertions(+)
include/linux/kvm_host.h +4 -0
index 0520cd8d03cc..eeaf4e73317c 100644
@@ -2389,4 +2389,8 @@ static inline int kvm_gmem_get_pfn(struct kvm *kvm,
 }
 #endif /* CONFIG_KVM_PRIVATE_MEM */
 
+void kvm_arch_vcpu_pre_memory_mapping(struct kvm_vcpu *vcpu);
+int kvm_arch_vcpu_memory_mapping(struct kvm_vcpu *vcpu,
+				 struct kvm_memory_mapping *mapping);
+
 #endif
include/uapi/linux/kvm.h +10 -0
index c3308536482b..5e2b28934aa9 100644
@@ -1155,6 +1155,7 @@ struct kvm_ppc_resize_hpt {
 #define KVM_CAP_MEMORY_ATTRIBUTES 233
 #define KVM_CAP_GUEST_MEMFD 234
 #define KVM_CAP_VM_TYPES 235
+#define KVM_CAP_MEMORY_MAPPING 236
 
 #ifdef KVM_CAP_IRQ_ROUTING
 
@@ -2227,4 +2228,13 @@ struct kvm_create_guest_memfd {
 	__u64 reserved[6];
 };
 
+#define KVM_MEMORY_MAPPING	_IOWR(KVMIO, 0xd5, struct kvm_memory_mapping)
+
+struct kvm_memory_mapping {
+	__u64 base_gfn;
+	__u64 nr_pages;
+	__u64 flags;
+	__u64 source;
+};
+
 #endif /* __LINUX_KVM_H */
virt/kvm/kvm_main.c +67 -0

79 lines not on this page · raw message

Signature · 1 line
2.25.1
#75 lore raw

Re: [PATCH v19 014/130] KVM: Add KVM vcpu ioctl to pre-populate guest memory

Quoted 78 lines · On 2/26/24 16:25, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Add new ioctl KVM_MEMORY_MAPPING in the kvm common code. It iterates on the memory range and call arch specific function. Add stub function as weak symbol.

---
v19:
- newly added

---
 include/linux/kvm_host.h |  4 +++
 include/uapi/linux/kvm.h | 10 ++++++
 virt/kvm/kvm_main.c      | 67 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 81 insertions(+)
include/linux/kvm_host.h +4 -0
index 0520cd8d03cc..eeaf4e73317c 100644
@@ -2389,4 +2389,8 @@ static inline int kvm_gmem_get_pfn(struct kvm *kvm,
 }
 #endif /* CONFIG_KVM_PRIVATE_MEM */
 
+void kvm_arch_vcpu_pre_memory_mapping(struct kvm_vcpu *vcpu);
+int kvm_arch_vcpu_memory_mapping(struct kvm_vcpu *vcpu,
+				 struct kvm_memory_mapping *mapping);
+
 #endif
include/uapi/linux/kvm.h +10 -0
index c3308536482b..5e2b28934aa9 100644
@@ -1155,6 +1155,7 @@ struct kvm_ppc_resize_hpt {
 #define KVM_CAP_MEMORY_ATTRIBUTES 233
 #define KVM_CAP_GUEST_MEMFD 234
 #define KVM_CAP_VM_TYPES 235
+#define KVM_CAP_MEMORY_MAPPING 236
 
 #ifdef KVM_CAP_IRQ_ROUTING
 
@@ -2227,4 +2228,13 @@ struct kvm_create_guest_memfd {
 	__u64 reserved[6];
 };
 
+#define KVM_MEMORY_MAPPING	_IOWR(KVMIO, 0xd5, struct kvm_memory_mapping)
+
+struct kvm_memory_mapping {
+	__u64 base_gfn;
+	__u64 nr_pages;
+	__u64 flags;
+	__u64 source;
+};
+
 #endif /* __LINUX_KVM_H */
virt/kvm/kvm_main.c +15 -0
index 0349e1f241d1..2f0a8e28795e 100644
@@ -4409,6 +4409,62 @@ static int kvm_vcpu_ioctl_get_stats_fd(struct kvm_vcpu *vcpu)
 	return fd;
 }
 
+__weak void kvm_arch_vcpu_pre_memory_mapping(struct kvm_vcpu *vcpu)
+{
+}
+
+__weak int kvm_arch_vcpu_memory_mapping(struct kvm_vcpu *vcpu,
+					struct kvm_memory_mapping *mapping)
+{
+	return -EOPNOTSUPP;
+}
+
+static int kvm_vcpu_memory_mapping(struct kvm_vcpu *vcpu,
+				   struct kvm_memory_mapping *mapping)
+{
+	bool added = false;
+	int idx, r = 0;

virt/kvm/kvm_main.c static int kvm_vcpu_ioctl_get_stats_fd(struct kvm_vcpu *vcpu) · line 4436

Quoted 10 lines
virt/kvm/kvm_main.c +10 -0
+
+	/* flags isn't used yet. */
+	if (mapping->flags)
+		return -EINVAL;
+
+	/* Sanity check */
+	if (!IS_ALIGNED(mapping->source, PAGE_SIZE) ||
+	    !mapping->nr_pages ||
+	    mapping->nr_pages & GENMASK_ULL(63, 63 - PAGE_SHIFT) ||
+	    mapping->base_gfn + mapping->nr_pages <= mapping->base_gfn)

I suppose !mapping->nr_pages can be deleted as this line can cover it.

Quoted 35 lines
virt/kvm/kvm_main.c +31 -0
+		return -EINVAL;
+
+	vcpu_load(vcpu);
+	idx = srcu_read_lock(&vcpu->kvm->srcu);
+	kvm_arch_vcpu_pre_memory_mapping(vcpu);
+
+	while (mapping->nr_pages) {
+		if (signal_pending(current)) {
+			r = -ERESTARTSYS;
+			break;
+		}
+
+		if (need_resched())
+			cond_resched();
+
+		r = kvm_arch_vcpu_memory_mapping(vcpu, mapping);
+		if (r)
+			break;
+
+		added = true;
+	}
+
+	srcu_read_unlock(&vcpu->kvm->srcu, idx);
+	vcpu_put(vcpu);
+
+	if (added && mapping->nr_pages > 0)
+		r = -EAGAIN;
+
+	return r;
+}
+
 static long kvm_vcpu_ioctl(struct file *filp,
 			   unsigned int ioctl, unsigned long arg)
 {

virt/kvm/kvm_main.c static long kvm_vcpu_ioctl(struct file *filp, · line 4675

Quoted 10 lines
virt/kvm/kvm_main.c +7 -0
@@ -4610,6 +4666,17 @@ static long kvm_vcpu_ioctl(struct file *filp,
 		r = kvm_vcpu_ioctl_get_stats_fd(vcpu);
 		break;
 	}
+	case KVM_MEMORY_MAPPING: {
+		struct kvm_memory_mapping mapping;
+
+		r = -EFAULT;
+		if (copy_from_user(&mapping, argp, sizeof(mapping)))
+			break;
+		r = kvm_vcpu_memory_mapping(vcpu, &mapping);

return value r should be checked before copy_to_user

Regards
Yin, Fengwei

Quoted 7 lines
virt/kvm/kvm_main.c +4 -0
+		if (copy_to_user(argp, &mapping, sizeof(mapping)))
+			r = -EFAULT;
+		break;
+	}
 	default:
 		r = kvm_arch_vcpu_ioctl(filp, ioctl, arg);
 	}
#76 lore raw

Re: [PATCH v19 014/130] KVM: Add KVM vcpu ioctl to pre-populate guest memory

Quoted 72 lines · Yin Fengwei
Quoted 23 lines
virt/kvm/kvm_main.c +15 -0
index 0349e1f241d1..2f0a8e28795e 100644
@@ -4409,6 +4409,62 @@ static int kvm_vcpu_ioctl_get_stats_fd(struct kvm_vcpu *vcpu)
 	return fd;
 }
 
+__weak void kvm_arch_vcpu_pre_memory_mapping(struct kvm_vcpu *vcpu)
+{
+}
+
+__weak int kvm_arch_vcpu_memory_mapping(struct kvm_vcpu *vcpu,
+					struct kvm_memory_mapping *mapping)
+{
+	return -EOPNOTSUPP;
+}
+
+static int kvm_vcpu_memory_mapping(struct kvm_vcpu *vcpu,
+				   struct kvm_memory_mapping *mapping)
+{
+	bool added = false;
+	int idx, r = 0;

virt/kvm/kvm_main.c static int kvm_vcpu_ioctl_get_stats_fd(struct kvm_vcpu *vcpu) · line 4436

Quoted 10 lines
virt/kvm/kvm_main.c +10 -0
+
+	/* flags isn't used yet. */
+	if (mapping->flags)
+		return -EINVAL;
+
+	/* Sanity check */
+	if (!IS_ALIGNED(mapping->source, PAGE_SIZE) ||
+	    !mapping->nr_pages ||
+	    mapping->nr_pages & GENMASK_ULL(63, 63 - PAGE_SHIFT) ||
+	    mapping->base_gfn + mapping->nr_pages <= mapping->base_gfn)

I suppose !mapping->nr_pages can be deleted as this line can cover it.

Quoted 38 lines
virt/kvm/kvm_main.c +31 -0
+		return -EINVAL;
+
+	vcpu_load(vcpu);
+	idx = srcu_read_lock(&vcpu->kvm->srcu);
+	kvm_arch_vcpu_pre_memory_mapping(vcpu);
+
+	while (mapping->nr_pages) {
+		if (signal_pending(current)) {
+			r = -ERESTARTSYS;
+			break;
+		}
+
+		if (need_resched())
+			cond_resched();
+
+		r = kvm_arch_vcpu_memory_mapping(vcpu, mapping);
+		if (r)
+			break;
+
+		added = true;
+	}
+
+	srcu_read_unlock(&vcpu->kvm->srcu, idx);
+	vcpu_put(vcpu);
+
+	if (added && mapping->nr_pages > 0)
+		r = -EAGAIN;
+
+	return r;
+}
+
 static long kvm_vcpu_ioctl(struct file *filp,
 			   unsigned int ioctl, unsigned long arg)
 {
@@ -4610,6 +4666,17 @@ static long kvm_vcpu_ioctl(struct file *filp,
 		r = kvm_vcpu_ioctl_get_stats_fd(vcpu);
 		break;
 	}
Quoted 8 lines

virt/kvm/kvm_main.c static long kvm_vcpu_ioctl(struct file *filp, · line 4675

Quoted 7 lines
virt/kvm/kvm_main.c +7 -0
+	case KVM_MEMORY_MAPPING: {
+		struct kvm_memory_mapping mapping;
+
+		r = -EFAULT;
+		if (copy_from_user(&mapping, argp, sizeof(mapping)))
+			break;
+		r = kvm_vcpu_memory_mapping(vcpu, &mapping);

return value r should be checked before copy_to_user

That's intentional to tell the mapping is partially or fully processed regardless that error happened or not.

Quoted 13 lines

Regards
Yin, Fengwei

Quoted 7 lines
virt/kvm/kvm_main.c +4 -0
+		if (copy_to_user(argp, &mapping, sizeof(mapping)))
+			r = -EFAULT;
+		break;
+	}
 	default:
 		r = kvm_arch_vcpu_ioctl(filp, ioctl, arg);
 	}
Signature · 1 line
Isaku Yamahata <[email protected]>
#77 lore raw

PATCH v19 15/130 KVM: Document KVM_MEMORY_MAPPING ioctl

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Adds documentation of KVM_MEMORY_MAPPING ioctl.

---
v19:
- newly added

---
 Documentation/virt/kvm/api.rst | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)
Documentation/virt/kvm/api.rst +28 -0
index 3ec0b7a455a0..667dc58f7d2f 100644
@@ -6323,6 +6323,34 @@ a single guest_memfd file, but the bound ranges must not overlap).
 
 See KVM_SET_USER_MEMORY_REGION2 for additional details.
 
+4.143 KVM_MEMORY_MAPPING
+------------------------
+
+:Capability: KVM_CAP_MEMORY_MAPPING
+:Architectures: none
+:Type: vcpu ioctl
+:Parameters: struct kvm_memory_mapping(in/out)
+:Returns: 0 on success, <0 on error
+
+KVM_MEMORY_MAPPING populates guest memory without running vcpu.
+
+::
+
+  struct kvm_memory_mapping {
+	__u64 base_gfn;
+	__u64 nr_pages;
+	__u64 flags;
+	__u64 source;
+  };
+
+KVM_MEMORY_MAPPING populates guest memory in the underlying mapping. If source
+is not zero and it's supported (it depends on underlying technology), the guest
+memory content is populated with the source.  If nr_pages is large, it may
+return -EAGAIN and the values (base_gfn and nr_pages. source if not zero) are
+updated to point the remaining range.
+
+The "flags" field is reserved for future extensions and must be '0'.
+
 5. The kvm_run structure
 ========================
 
Signature · 1 line
2.25.1
#78 lore raw

PATCH v19 16/130 KVM: x86/mmu: Introduce kvm_mmu_map_tdp_page() for use by TDX

In reply to [email protected]

From: Sean Christopherson <[email protected]>

Introduce a helper to directly (pun intended) fault-in a TDP page without having to go through the full page fault path. This allows TDX to get the resulting pfn and also allows the RET_PF_* enums to stay in mmu.c where they belong.

---
v19:
- Move up for KVM_MEMORY_MAPPING.
- Add goal_level for the caller to know how many pages are mapped.

v14 -> v15:
- Remove loop in kvm_mmu_map_tdp_page() and return error code based on RET_FP_xxx value to avoid potential infinite loop. The caller should loop on -EAGAIN instead now.

---
 arch/x86/kvm/mmu.h     |  3 +++
 arch/x86/kvm/mmu/mmu.c | 58 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 61 insertions(+)
arch/x86/kvm/mmu.h +3 -0
index 60f21bb4c27b..d96c93a25b3b 100644
@@ -183,6 +183,9 @@ static inline void kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
 	__kvm_mmu_refresh_passthrough_bits(vcpu, mmu);
 }
 
+int kvm_mmu_map_tdp_page(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code,
+			 u8 max_level, u8 *goal_level);
+
 /*
  * Check if a given access (described through the I/D, W/R and U/S bits of a
  * page fault error code pfec) causes a permission fault with the given PTE
arch/x86/kvm/mmu/mmu.c +58 -0

64 lines not on this page · raw message

Signature · 1 line
2.25.1
#79 lore raw

Re: [PATCH v19 016/130] KVM: x86/mmu: Introduce kvm_mmu_map_tdp_page() for use by TDX

Quoted 59 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Sean Christopherson <[email protected]>

Introduce a helper to directly (pun intended) fault-in a TDP page without having to go through the full page fault path. This allows TDX to get the resulting pfn and also allows the RET_PF_* enums to stay in mmu.c where they belong.

---
v19:
- Move up for KVM_MEMORY_MAPPING.
- Add goal_level for the caller to know how many pages are mapped.

v14 -> v15:
- Remove loop in kvm_mmu_map_tdp_page() and return error code based on RET_FP_xxx value to avoid potential infinite loop. The caller should loop on -EAGAIN instead now.

---
  arch/x86/kvm/mmu.h     |  3 +++
  arch/x86/kvm/mmu/mmu.c | 58 ++++++++++++++++++++++++++++++++++++++++++
  2 files changed, 61 insertions(+)
arch/x86/kvm/mmu.h +3 -0
index 60f21bb4c27b..d96c93a25b3b 100644
@@ -183,6 +183,9 @@ static inline void kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
  	__kvm_mmu_refresh_passthrough_bits(vcpu, mmu);
  }
 
+int kvm_mmu_map_tdp_page(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code,
+			 u8 max_level, u8 *goal_level);
+
  /*
   * Check if a given access (described through the I/D, W/R and U/S bits of a
   * page fault error code pfec) causes a permission fault with the given PTE
arch/x86/kvm/mmu/mmu.c +12 -0
index 61674d6b17aa..ca0c91f14063 100644
@@ -4615,6 +4615,64 @@ int kvm_tdp_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
  	return direct_page_fault(vcpu, fault);
  }
 
+int kvm_mmu_map_tdp_page(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code,
+			 u8 max_level, u8 *goal_level)
+{
+	int r;
+	struct kvm_page_fault fault = (struct kvm_page_fault) {
+		.addr = gpa,
+		.error_code = error_code,
+		.exec = error_code & PFERR_FETCH_MASK,
+		.write = error_code & PFERR_WRITE_MASK,
+		.present = error_code & PFERR_PRESENT_MASK,
+		.rsvd = error_code & PFERR_RSVD_MASK,
+		.user = error_code & PFERR_USER_MASK,

arch/x86/kvm/mmu/mmu.c int kvm_tdp_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault) · line 4639

Quoted 10 lines
arch/x86/kvm/mmu/mmu.c +10 -0
+		.prefetch = false,
+		.is_tdp = true,
+		.is_private = error_code & PFERR_GUEST_ENC_MASK,
+		.nx_huge_page_workaround_enabled = is_nx_huge_page_enabled(vcpu->kvm),
+	};
+
+	WARN_ON_ONCE(!vcpu->arch.mmu->root_role.direct);
+	fault.slot = kvm_vcpu_gfn_to_memslot(vcpu, fault.gfn);
+
+	r = mmu_topup_memory_caches(vcpu, false);

Does it need a cache topup here?
Both kvm_tdp_mmu_page_fault() and direct_page_fault() will call mmu_topup_memory_caches() when needed.

Quoted 39 lines
arch/x86/kvm/mmu/mmu.c +36 -0
+	if (r)
+		return r;
+
+	fault.max_level = max_level;
+	fault.req_level = PG_LEVEL_4K;
+	fault.goal_level = PG_LEVEL_4K;
+
+#ifdef CONFIG_X86_64
+	if (tdp_mmu_enabled)
+		r = kvm_tdp_mmu_page_fault(vcpu, &fault);
+	else
+#endif
+		r = direct_page_fault(vcpu, &fault);
+
+	if (is_error_noslot_pfn(fault.pfn) || vcpu->kvm->vm_bugged)
+		return -EFAULT;
+
+	switch (r) {
+	case RET_PF_RETRY:
+		return -EAGAIN;
+
+	case RET_PF_FIXED:
+	case RET_PF_SPURIOUS:
+		if (goal_level)
+			*goal_level = fault.goal_level;
+		return 0;
+
+	case RET_PF_CONTINUE:
+	case RET_PF_EMULATE:
+	case RET_PF_INVALID:
+	default:
+		return -EIO;
+	}
+}
+EXPORT_SYMBOL_GPL(kvm_mmu_map_tdp_page);
+
  static void nonpaging_init_context(struct kvm_mmu *context)
  {
  	context->page_fault = nonpaging_page_fault;
#80 lore raw

Re: [PATCH v19 016/130] KVM: x86/mmu: Introduce kvm_mmu_map_tdp_page() for use by TDX

Quoted 66 lines · Binbin Wu
Quoted 63 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Sean Christopherson <[email protected]>

Introduce a helper to directly (pun intended) fault-in a TDP page without having to go through the full page fault path. This allows TDX to get the resulting pfn and also allows the RET_PF_* enums to stay in mmu.c where they belong.

---
v19:
- Move up for KVM_MEMORY_MAPPING.
- Add goal_level for the caller to know how many pages are mapped.

v14 -> v15:
- Remove loop in kvm_mmu_map_tdp_page() and return error code based on RET_FP_xxx value to avoid potential infinite loop. The caller should loop on -EAGAIN instead now.

---
  arch/x86/kvm/mmu.h     |  3 +++
  arch/x86/kvm/mmu/mmu.c | 58 ++++++++++++++++++++++++++++++++++++++++++
  2 files changed, 61 insertions(+)
arch/x86/kvm/mmu.h +3 -0
index 60f21bb4c27b..d96c93a25b3b 100644
@@ -183,6 +183,9 @@ static inline void kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
  	__kvm_mmu_refresh_passthrough_bits(vcpu, mmu);
  }
+int kvm_mmu_map_tdp_page(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code,
+			 u8 max_level, u8 *goal_level);
+
  /*
   * Check if a given access (described through the I/D, W/R and U/S bits of a
   * page fault error code pfec) causes a permission fault with the given PTE
arch/x86/kvm/mmu/mmu.c +18 -0
index 61674d6b17aa..ca0c91f14063 100644
@@ -4615,6 +4615,64 @@ int kvm_tdp_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
  	return direct_page_fault(vcpu, fault);
  }
+int kvm_mmu_map_tdp_page(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code,
+			 u8 max_level, u8 *goal_level)
+{
+	int r;
+	struct kvm_page_fault fault = (struct kvm_page_fault) {
+		.addr = gpa,
+		.error_code = error_code,
+		.exec = error_code & PFERR_FETCH_MASK,
+		.write = error_code & PFERR_WRITE_MASK,
+		.present = error_code & PFERR_PRESENT_MASK,
+		.rsvd = error_code & PFERR_RSVD_MASK,
+		.user = error_code & PFERR_USER_MASK,
+		.prefetch = false,
+		.is_tdp = true,
+		.is_private = error_code & PFERR_GUEST_ENC_MASK,
+		.nx_huge_page_workaround_enabled = is_nx_huge_page_enabled(vcpu->kvm),
+	};
+
Quoted 8 lines

arch/x86/kvm/mmu/mmu.c int kvm_tdp_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault) · line 4639

Quoted 4 lines
arch/x86/kvm/mmu/mmu.c +4 -0
+	WARN_ON_ONCE(!vcpu->arch.mmu->root_role.direct);
+	fault.slot = kvm_vcpu_gfn_to_memslot(vcpu, fault.gfn);
+
+	r = mmu_topup_memory_caches(vcpu, false);

Does it need a cache topup here?
Both kvm_tdp_mmu_page_fault() and direct_page_fault() will call mmu_topup_memory_caches() when needed.

You're right. As the called function has changed, I missed to remove it.

Signature · 1 line
Isaku Yamahata <[email protected]>
#81 lore raw

PATCH v19 17/130 KVM: x86: Implement kvm_arch_{, pre_}vcpu_memory_mapping()

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Wire KVM_MEMORY_MAPPING ioctl to kvm_mmu_map_tdp_page() to populate guest memory.

---
v19:
- newly added

---
 arch/x86/kvm/x86.c | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)
arch/x86/kvm/x86.c +26 -0
index 48a61d283406..03dab4266172 100644
@@ -4663,6 +4663,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 	case KVM_CAP_VM_DISABLE_NX_HUGE_PAGES:
 	case KVM_CAP_IRQFD_RESAMPLE:
 	case KVM_CAP_MEMORY_FAULT_INFO:
+	case KVM_CAP_MEMORY_MAPPING:
 		r = 1;
 		break;
 	case KVM_CAP_EXIT_HYPERCALL:
@@ -5801,6 +5802,31 @@ static int kvm_vcpu_ioctl_enable_cap(struct kvm_vcpu *vcpu,
 	}
 }
 
+void kvm_arch_vcpu_pre_memory_mapping(struct kvm_vcpu *vcpu)
+{
+	kvm_mmu_reload(vcpu);
+}
+
+int kvm_arch_vcpu_memory_mapping(struct kvm_vcpu *vcpu,
+				 struct kvm_memory_mapping *mapping)
+{
+	u8 max_level = KVM_MAX_HUGEPAGE_LEVEL;
+	u64 error_code = PFERR_WRITE_MASK;
+	u8 goal_level = PG_LEVEL_4K;
+	int r;
+
+	r = kvm_mmu_map_tdp_page(vcpu, gfn_to_gpa(mapping->base_gfn), error_code,
+				 max_level, &goal_level);
+	if (r)
+		return r;
+
+	if (mapping->source)
+		mapping->source += KVM_HPAGE_SIZE(goal_level);
+	mapping->base_gfn += KVM_PAGES_PER_HPAGE(goal_level);
+	mapping->nr_pages -= KVM_PAGES_PER_HPAGE(goal_level);
+	return r;
+}
+
 long kvm_arch_vcpu_ioctl(struct file *filp,
 			 unsigned int ioctl, unsigned long arg)
 {
Signature · 1 line
2.25.1
#82 lore raw

PATCH v19 18/130 KVM: x86/mmu: Assume guest MMIOs are shared

In reply to [email protected]

From: Chao Gao <[email protected]>

TODO: Drop this patch once the common patch is merged.

When memory slot isn't found for kvm page fault, handle it as MMIO.

The guest of TDX_VM, SNP_VM, or SW_PROTECTED_VM don't necessarily convert the virtual MMIO range to shared before accessing it. When the guest tries to access the virtual device's MMIO without any private/shared conversion, An NPT fault or EPT violation is raised first to find private-shared mismatch. Don't raise KVM_EXIT_MEMORY_FAULT, fall back to KVM_PFN_NOLSLOT.

---
v19:
- Added slot check before kvm_faultin_private_pfn()
- updated comment
- rewrite the commit message

---
 arch/x86/kvm/mmu/mmu.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)
arch/x86/kvm/mmu/mmu.c +12 -2
index ca0c91f14063..c45252ed2ffd 100644
@@ -4342,6 +4342,7 @@ static int kvm_faultin_pfn_private(struct kvm_vcpu *vcpu,
 static int __kvm_faultin_pfn(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
 {
 	struct kvm_memory_slot *slot = fault->slot;
+	bool force_mmio;
 	bool async;
 
 	/*
@@ -4371,12 +4372,21 @@ static int __kvm_faultin_pfn(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
 			return RET_PF_EMULATE;
 	}
 
-	if (fault->is_private != kvm_mem_is_private(vcpu->kvm, fault->gfn)) {
+	/*
+	 * !fault->slot means MMIO for SNP and TDX.  Don't require explicit GPA
+	 * conversion for MMIO because MMIO is assigned at the boot time.  Fall
+	 * to !is_private case to get pfn = KVM_PFN_NOSLOT.
+	 */
+	force_mmio = !slot &&
+		vcpu->kvm->arch.vm_type != KVM_X86_DEFAULT_VM &&
+		vcpu->kvm->arch.vm_type != KVM_X86_SW_PROTECTED_VM;
+	if (!force_mmio &&
+	    fault->is_private != kvm_mem_is_private(vcpu->kvm, fault->gfn)) {
 		kvm_mmu_prepare_memory_fault_exit(vcpu, fault);
 		return -EFAULT;
 	}
 
-	if (fault->is_private)
+	if (!force_mmio && fault->is_private)
 		return kvm_faultin_pfn_private(vcpu, fault);
 
 	async = false;
Signature · 1 line
2.25.1
#83 lore raw

Re: [PATCH v19 018/130] KVM: x86/mmu: Assume guest MMIOs are shared

Quoted 3 lines · On Mon, 2024-02-26 at 00:25 -0800, [email protected] wrote:

From: Chao Gao <[email protected]>

TODO: Drop this patch once the common patch is merged.

What is this TODO talking about?

Quoted 8 lines

When memory slot isn't found for kvm page fault, handle it as MMIO.

The guest of TDX_VM, SNP_VM, or SW_PROTECTED_VM don't necessarily convert the virtual MMIO range to shared before accessing it.  When the guest tries to access the virtual device's MMIO without any private/shared conversion, An NPT fault or EPT violation is raised first to find private-shared mismatch.  Don't raise KVM_EXIT_MEMORY_FAULT, fall back to KVM_PFN_NOLSLOT.

If this is general KVM_X86_SW_PROTECTED_VM behavior, can we pull it out of the TDX series?

Quoted 4 lines

---

#84 lore raw

Re: [PATCH v19 018/130] KVM: x86/mmu: Assume guest MMIOs are shared

Quoted 6 lines · Rick P
Quoted 3 lines · On Mon, 2024-02-26 at 00:25 -0800, [email protected] wrote:

From: Chao Gao <[email protected]>

TODO: Drop this patch once the common patch is merged.

What is this TODO talking about?

https://lore.kernel.org/all/[email protected]/

This patch was shot down and need to fix it in guest side. TDVF.

Signature · 1 line
Isaku Yamahata <[email protected]>
#85 lore raw

Re: [PATCH v19 018/130] KVM: x86/mmu: Assume guest MMIOs are shared

Quoted 6 lines · Isaku Yamahata
Quoted 3 lines · Rick P
Quoted 2 lines · On Mon, 2024-02-26 at 00:25 -0800, [email protected] wrote:

From: Chao Gao <[email protected]>

Quoted 7 lines
Quoted 3 lines
Quoted 1 line

TODO: Drop this patch once the common patch is merged.

What is this TODO talking about?

https://lore.kernel.org/all/[email protected]/

This patch was shot down and need to fix it in guest side. TDVF.

It needs a firmware fix? Is there any firmware that works (boot any TD) with this patch missing?

#86 lore raw

Re: [PATCH v19 018/130] KVM: x86/mmu: Assume guest MMIOs are shared

Quoted 9 lines · Rick P
Quoted 8 lines · Isaku Yamahata
Quoted 5 lines · Rick P
Quoted 3 lines · On Mon, 2024-02-26 at 00:25 -0800, [email protected] wrote:

From: Chao Gao <[email protected]>

TODO: Drop this patch once the common patch is merged.

Quoted 7 lines
Quoted 5 lines
Quoted 1 line

What is this TODO talking about?

https://lore.kernel.org/all/[email protected]/

This patch was shot down and need to fix it in guest side. TDVF.

It needs a firmware fix? Is there any firmware that works (boot any TD) with this patch missing?

Yes. Config-B (OvmfPkg/IntelTdx/IntelTdxX64.dsc) works for me without this patch. I'm looking into config-A(OvmfPkg/OvmfPkgX64.dsc) now.

Signature · 1 line
Isaku Yamahata <[email protected]>
#87 lore raw

PATCH v19 19/130 KVM: x86: Add is_vm_type_supported callback

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

For SEV_SNP and TDX, allow the backend can override the supported vm type. Add KVM_X86_SNP_VM and KVM_X86_TDX_VM to reserve the bit.

---
v19:
- Mention KVM_X86_SNP_VM to the commit message

v18:
- include into TDX KVM patch series v18

Changes v3 -> v4:
- Added KVM_X86_SNP_VM

Changes v2 -> v3:
- no change
- didn't bother to rename KVM_X86_PROTECTED_VM to KVM_X86_SW_PROTECTED_VM

Changes v1 -> v2
- no change

---
 arch/x86/include/asm/kvm-x86-ops.h |  1 +
 arch/x86/include/asm/kvm_host.h    |  1 +
 arch/x86/include/uapi/asm/kvm.h    |  2 ++
 arch/x86/kvm/svm/svm.c             |  7 +++++++
 arch/x86/kvm/vmx/vmx.c             |  7 +++++++
 arch/x86/kvm/x86.c                 | 12 +++++++++++-
 arch/x86/kvm/x86.h                 |  2 ++
 7 files changed, 31 insertions(+), 1 deletion(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 156832f01ebe..8be71a5c5c87 100644
@@ -20,6 +20,7 @@ KVM_X86_OP(hardware_disable)
 KVM_X86_OP(hardware_unsetup)
 KVM_X86_OP(has_emulated_msr)
 KVM_X86_OP(vcpu_after_set_cpuid)
+KVM_X86_OP(is_vm_type_supported)
 KVM_X86_OP(vm_init)
 KVM_X86_OP_OPTIONAL(vm_destroy)
 KVM_X86_OP_OPTIONAL_RET0(vcpu_precreate)
arch/x86/include/asm/kvm_host.h +1 -0
index 28314e7d546c..37cda8aa07b6 100644
@@ -1603,6 +1603,7 @@ struct kvm_x86_ops {
 	bool (*has_emulated_msr)(struct kvm *kvm, u32 index);
 	void (*vcpu_after_set_cpuid)(struct kvm_vcpu *vcpu);
 
+	bool (*is_vm_type_supported)(unsigned long vm_type);
 	unsigned int vm_size;
 	int (*vm_init)(struct kvm *kvm);
 	void (*vm_destroy)(struct kvm *kvm);
arch/x86/include/uapi/asm/kvm.h +2 -0
index a448d0964fc0..aa7a56a47564 100644
@@ -564,5 +564,7 @@ struct kvm_pmu_event_filter {
 
 #define KVM_X86_DEFAULT_VM	0
 #define KVM_X86_SW_PROTECTED_VM	1
+#define KVM_X86_TDX_VM		2
+#define KVM_X86_SNP_VM		3
 
 #endif /* _ASM_X86_KVM_H */
arch/x86/kvm/svm/svm.c +7 -0
index e90b429c84f1..f76dd52d29ba 100644
@@ -4886,6 +4886,12 @@ static void svm_vm_destroy(struct kvm *kvm)
 	sev_vm_destroy(kvm);
 }
 
+static bool svm_is_vm_type_supported(unsigned long type)
+{
+	/* FIXME: Check if CPU is capable of SEV-SNP. */
+	return __kvm_is_vm_type_supported(type);
+}
+
 static int svm_vm_init(struct kvm *kvm)
 {
 	if (!pause_filter_count || !pause_filter_thresh)
@@ -4914,6 +4920,7 @@ static struct kvm_x86_ops svm_x86_ops __initdata = {
 	.vcpu_free = svm_vcpu_free,
 	.vcpu_reset = svm_vcpu_reset,
 
+	.is_vm_type_supported = svm_is_vm_type_supported,
 	.vm_size = sizeof(struct kvm_svm),
 	.vm_init = svm_vm_init,
 	.vm_destroy = svm_vm_destroy,
arch/x86/kvm/vmx/vmx.c +7 -0
index 1111d9d08903..fca3457dd050 100644
@@ -7541,6 +7541,12 @@ static int vmx_vcpu_create(struct kvm_vcpu *vcpu)
 	return err;
 }
 
+static bool vmx_is_vm_type_supported(unsigned long type)
+{
+	/* TODO: Check if TDX is supported. */
+	return __kvm_is_vm_type_supported(type);
+}
+
 #define L1TF_MSG_SMT "L1TF CPU bug present and SMT on, data leak possible. See CVE-2018-3646 and https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/l1tf.html for details.\n"
 #define L1TF_MSG_L1D "L1TF CPU bug present and virtualization mitigation disabled, data leak possible. See CVE-2018-3646 and https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/l1tf.html for details.\n"
 
@@ -8263,6 +8269,7 @@ static struct kvm_x86_ops vmx_x86_ops __initdata = {
 	.hardware_disable = vmx_hardware_disable,
 	.has_emulated_msr = vmx_has_emulated_msr,
 
+	.is_vm_type_supported = vmx_is_vm_type_supported,
 	.vm_size = sizeof(struct kvm_vmx),
 	.vm_init = vmx_vm_init,
 	.vm_destroy = vmx_vm_destroy,
arch/x86/kvm/x86.c +11 -1
index 03dab4266172..442b356e4939 100644
@@ -4576,12 +4576,18 @@ static int kvm_ioctl_get_supported_hv_cpuid(struct kvm_vcpu *vcpu,
 }
 #endif
 
-static bool kvm_is_vm_type_supported(unsigned long type)
+bool __kvm_is_vm_type_supported(unsigned long type)
 {
 	return type == KVM_X86_DEFAULT_VM ||
 	       (type == KVM_X86_SW_PROTECTED_VM &&
 		IS_ENABLED(CONFIG_KVM_SW_PROTECTED_VM) && tdp_enabled);
 }
+EXPORT_SYMBOL_GPL(__kvm_is_vm_type_supported);
+
+static bool kvm_is_vm_type_supported(unsigned long type)
+{
+	return static_call(kvm_x86_is_vm_type_supported)(type);
+}
 
 int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 {
@@ -4784,6 +4790,10 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 		r = BIT(KVM_X86_DEFAULT_VM);
 		if (kvm_is_vm_type_supported(KVM_X86_SW_PROTECTED_VM))
 			r |= BIT(KVM_X86_SW_PROTECTED_VM);
+		if (kvm_is_vm_type_supported(KVM_X86_TDX_VM))
+			r |= BIT(KVM_X86_TDX_VM);
+		if (kvm_is_vm_type_supported(KVM_X86_SNP_VM))
+			r |= BIT(KVM_X86_SNP_VM);
 		break;
 	default:
 		break;
arch/x86/kvm/x86.h +2 -0
index 2f7e19166658..4e40c23d66ed 100644
@@ -9,6 +9,8 @@
 #include "kvm_cache_regs.h"
 #include "kvm_emulate.h"
 
+bool __kvm_is_vm_type_supported(unsigned long type);
+
 struct kvm_caps {
 	/* control of guest tsc rate supported? */
 	bool has_tsc_control;
Signature · 1 line
2.25.1
#88 lore raw

Re: [PATCH v19 019/130] KVM: x86: Add is_vm_type_supported callback

Quoted 6 lines
arch/x86/kvm/x86.c +1 -1
-static bool kvm_is_vm_type_supported(unsigned long type)
+bool __kvm_is_vm_type_supported(unsigned long type)
{
	return type == KVM_X86_DEFAULT_VM ||
	       (type == KVM_X86_SW_PROTECTED_VM &&
		IS_ENABLED(CONFIG_KVM_SW_PROTECTED_VM) && tdp_enabled);

maybe just do:

	switch (type) {
	case KVM_X86_DEFAULT_VM:
		return true;
	case KVM_X86_SW_PROTECTED_VM:
		return IS_ENABLED(CONFIG_KVM_SW_PROTECTED_VM) && tdp_enabled;
	default:
		return static_call(kvm_x86_is_vm_type_supported)(type);
	}

There are two benefits
1) switch/case improves readability a little.
2) no need to expose __kvm_is_vm_type_supported()

Quoted 12 lines

}

arch/x86/kvm/x86.c +6 -0
+EXPORT_SYMBOL_GPL(__kvm_is_vm_type_supported);
 
+
+static bool kvm_is_vm_type_supported(unsigned long type)
+{
+	return static_call(kvm_x86_is_vm_type_supported)(type);
+}

int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) {

arch/x86/kvm/x86.c int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) · line 4796

Quoted 7 lines
arch/x86/kvm/x86.c +4 -0
@@ -4784,6 +4790,10 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 		r = BIT(KVM_X86_DEFAULT_VM);
 		if (kvm_is_vm_type_supported(KVM_X86_SW_PROTECTED_VM))
 			r |= BIT(KVM_X86_SW_PROTECTED_VM);
+		if (kvm_is_vm_type_supported(KVM_X86_TDX_VM))
+			r |= BIT(KVM_X86_TDX_VM);
+		if (kvm_is_vm_type_supported(KVM_X86_SNP_VM))
+			r |= BIT(KVM_X86_SNP_VM);

maybe use a for-loop?

#89 lore raw

Re: [PATCH v19 019/130] KVM: x86: Add is_vm_type_supported callback

Quoted 17 lines · Chao Gao
Quoted 6 lines
arch/x86/kvm/x86.c +1 -1
-static bool kvm_is_vm_type_supported(unsigned long type)
+bool __kvm_is_vm_type_supported(unsigned long type)
{
	return type == KVM_X86_DEFAULT_VM ||
	       (type == KVM_X86_SW_PROTECTED_VM &&
		IS_ENABLED(CONFIG_KVM_SW_PROTECTED_VM) && tdp_enabled);

maybe just do:

	switch (type) {
	case KVM_X86_DEFAULT_VM:
		return true;
	case KVM_X86_SW_PROTECTED_VM:
		return IS_ENABLED(CONFIG_KVM_SW_PROTECTED_VM) && tdp_enabled;
	default:
		return static_call(kvm_x86_is_vm_type_supported)(type);
	}
Quoted 3 lines

There are two benefits
1) switch/case improves readability a little.
2) no need to expose __kvm_is_vm_type_supported()

The following[1] patch will supersede this patch. Will drop this patch.

[1] https://lore.kernel.org/kvm/[email protected]/

Signature · 1 line
Isaku Yamahata <[email protected]>
#90 lore raw

PATCH v19 20/130 KVM: VMX: Move out vmx_x86_ops to 'main.c' to wrap VMX and TDX

In reply to [email protected]

From: Sean Christopherson <[email protected]>

KVM accesses Virtual Machine Control Structure (VMCS) with VMX instructions to operate on VM. TDX doesn't allow VMM to operate VMCS directly. Instead, TDX has its own data structures, and TDX SEAMCALL APIs for VMM to indirectly operate those data structures. This means we must have a TDX version of kvm_x86_ops.

The existing global struct kvm_x86_ops already defines an interface which fits with TDX. But kvm_x86_ops is system-wide, not per-VM structure. To allow VMX to coexist with TDs, the kvm_x86_ops callbacks will have wrappers "if (tdx) tdx_op() else vmx_op()" to switch VMX or TDX at run time.

To split the runtime switch, the VMX implementation, and the TDX implementation, add main.c, and move out the vmx_x86_ops hooks in preparation for adding TDX, which can coexist with VMX, i.e. KVM can run both VMs and TDs. Use 'vt' for the naming scheme as a nod to VT-x and as a concatenation of VmxTdx.

The current code looks as follows.
In vmx.c
static vmx_op() { ... }

  static struct kvm_x86_ops vmx_x86_ops = {
        .op = vmx_op,

initialization code

The eventually converted code will look like In vmx.c, keep the VMX operations.
vmx_op() { ... }
VMX initialization
In tdx.c, define the TDX operations.
tdx_op() { ... }
TDX initialization
In x86_ops.h, declare the VMX and TDX operations.
vmx_op();
tdx_op();
In main.c, define common wrappers for VMX and TDX.
static vt_ops() { if (tdx) tdx_ops() else vmx_ops() }

  static struct kvm_x86_ops vt_x86_ops = {
        .op = vt_op,

initialization to call VMX and TDX initialization

Opportunistically, fix the name inconsistency from vmx_create_vcpu() and vmx_free_vcpu() to vmx_vcpu_create() and vmx_vcpu_free().

---
v19:
- Move down the declaration of vmx_hardware_setup() in x86_ops.h. Xiaoyao

v18:
- Add Reviewed-by: Binbin Wu
- fix indent alignments pointed by Binbin Wu

---
 arch/x86/kvm/Makefile      |   2 +-
 arch/x86/kvm/vmx/main.c    | 169 +++++++++++++++++
 arch/x86/kvm/vmx/vmx.c     | 378 ++++++++++---------------------------
 arch/x86/kvm/vmx/x86_ops.h | 124 ++++++++++++
 4 files changed, 397 insertions(+), 276 deletions(-)
 create mode 100644 arch/x86/kvm/vmx/main.c
 create mode 100644 arch/x86/kvm/vmx/x86_ops.h
arch/x86/kvm/Makefile +1 -1
index 475b5fa917a6..274df24b647f 100644
@@ -20,7 +20,7 @@ kvm-$(CONFIG_KVM_XEN)	+= xen.o
 kvm-$(CONFIG_KVM_SMM)	+= smm.o
 
 kvm-intel-y		+= vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o vmx/vmcs12.o \
-			   vmx/nested.o vmx/posted_intr.o
+			   vmx/nested.o vmx/posted_intr.o vmx/main.o
 
 kvm-intel-$(CONFIG_X86_SGX_KVM)	+= vmx/sgx.o
 kvm-intel-$(CONFIG_KVM_HYPERV)	+= vmx/hyperv.o vmx/hyperv_evmcs.o
arch/x86/kvm/vmx/main.c new file +169 -0

169 lines not on this page · raw message

arch/x86/kvm/vmx/vmx.c +103 -275

933 lines not on this page · raw message

arch/x86/kvm/vmx/x86_ops.h new file +124 -0

124 lines not on this page · raw message

Signature · 1 line
2.25.1
#91 lore raw

PATCH v19 21/130 KVM: x86/vmx: initialize loaded_vmcss_on_cpu in vmx_init()

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

vmx_hardware_disable() accesses loaded_vmcss_on_cpu via hardware_disable_all(). To allow hardware_enable/disable_all() before kvm_init(), initialize it in before kvm_x86_vendor_init() in vmx_init() so that tdx module initialization, hardware_setup method, can reference the variable.

---
v19:
- Fix the subject to match the patch by Yuan

v18:
- Move the vmcss_on_cpu initialization from vmx_hardware_setup() to early point of vmx_init() by Binbin

---
 arch/x86/kvm/vmx/vmx.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)
arch/x86/kvm/vmx/vmx.c +5 -4
index 434f5aaef030..8af0668e4dca 100644
@@ -8539,6 +8539,10 @@ static int __init vmx_init(void)
 	 */
 	hv_init_evmcs();
 
+	/* vmx_hardware_disable() accesses loaded_vmcss_on_cpu. */
+	for_each_possible_cpu(cpu)
+		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
+
 	r = kvm_x86_vendor_init(&vt_init_ops);
 	if (r)
 		return r;
@@ -8554,11 +8558,8 @@ static int __init vmx_init(void)
 	if (r)
 		goto err_l1d_flush;
 
-	for_each_possible_cpu(cpu) {
-		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
-
+	for_each_possible_cpu(cpu)
 		pi_init_cpu(cpu);
-	}
 
 	cpu_emergency_register_virt_callback(vmx_emergency_disable);
 
Signature · 1 line
2.25.1
Reviewed-by on v19 21/130
#92 lore raw

Re: [PATCH v19 021/130] KVM: x86/vmx: initialize loaded_vmcss_on_cpu in vmx_init()

Quoted 10 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

vmx_hardware_disable() accesses loaded_vmcss_on_cpu via hardware_disable_all(). To allow hardware_enable/disable_all() before kvm_init(), initialize it in before kvm_x86_vendor_init() in vmx_init() so that tdx module initialization, hardware_setup method, can reference the variable.

The shortlog should be this?
KVM: VMX: Initialize loaded_vmcss_on_cpu in vmx_init()

Others,

Quoted 42 lines

---
v19:
- Fix the subject to match the patch by Yuan

v18:
- Move the vmcss_on_cpu initialization from vmx_hardware_setup() to early point of vmx_init() by Binbin

---
  arch/x86/kvm/vmx/vmx.c | 9 +++++----
  1 file changed, 5 insertions(+), 4 deletions(-)
arch/x86/kvm/vmx/vmx.c +5 -4
index 434f5aaef030..8af0668e4dca 100644
@@ -8539,6 +8539,10 @@ static int __init vmx_init(void)
  	 */
  	hv_init_evmcs();
 
+	/* vmx_hardware_disable() accesses loaded_vmcss_on_cpu. */
+	for_each_possible_cpu(cpu)
+		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
+
  	r = kvm_x86_vendor_init(&vt_init_ops);
  	if (r)
  		return r;
@@ -8554,11 +8558,8 @@ static int __init vmx_init(void)
  	if (r)
  		goto err_l1d_flush;
 
-	for_each_possible_cpu(cpu) {
-		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
-
+	for_each_possible_cpu(cpu)
  		pi_init_cpu(cpu);
-	}
 
  	cpu_emergency_register_virt_callback(vmx_emergency_disable);
 
#93 lore raw

Re: [PATCH v19 021/130] KVM: x86/vmx: initialize loaded_vmcss_on_cpu in vmx_init()

Quoted 11 lines · Binbin Wu
Quoted 8 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

vmx_hardware_disable() accesses loaded_vmcss_on_cpu via hardware_disable_all(). To allow hardware_enable/disable_all() before kvm_init(), initialize it in before kvm_x86_vendor_init() in vmx_init() so that tdx module initialization, hardware_setup method, can reference the variable.

Quoted 5 lines
Quoted 2 lines

The shortlog should be this?
KVM: VMX: Initialize loaded_vmcss_on_cpu in vmx_init()

Yes. I also will fix the shortlog in the next patch.

Signature · 1 line
Isaku Yamahata <[email protected]>
#94 lore raw

PATCH v19 22/130 KVM: x86/vmx: Refactor KVM VMX module init/exit functions

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Currently, KVM VMX module initialization/exit functions are a single function each. Refactor KVM VMX module initialization functions into KVM common part and VMX part so that TDX specific part can be added cleanly. Opportunistically refactor module exit function as well.

The current module initialization flow is,
0.) Check if VMX is supported,
1.) hyper-v specific initialization,
2.) system-wide x86 specific and vendor specific initialization, 3.) Final VMX specific system-wide initialization,
4.) calculate the sizes of VMX kvm structure and VMX vcpu structure, 5.) report those sizes to the KVM common layer and KVM common
initialization

Refactor the KVM VMX module initialization function into functions with a wrapper function to separate VMX logic in vmx.c from a file, main.c, common among VMX and TDX. Introduce a wrapper function for vmx_init().

The KVM architecture common layer allocates struct kvm with reported size for architecture-specific code. The KVM VMX module defines its structure as struct vmx_kvm { struct kvm; VMX specific members;} and uses it as struct vmx kvm. Similar for vcpu structure. TDX KVM patches will define TDX specific kvm and vcpu structures.

The current module exit function is also a single function, a combination of VMX specific logic and common KVM logic. Refactor it into VMX specific logic and KVM common logic. This is just refactoring to keep the VMX specific logic in vmx.c from main.c.

---
v19:
- Eliminate the unnecessary churn with vmx_hardware_setup() by Xiaoyao

v18:
- Move loaded_vmcss_on_cpu initialization to vt_init() before kvm_x86_vendor_init().
- added __init to an empty stub fucntion, hv_init_evmcs().

---
 arch/x86/kvm/vmx/main.c    | 54 ++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/vmx.c     | 60 +++++---------------------------------
 arch/x86/kvm/vmx/x86_ops.h | 14 +++++++++
 3 files changed, 75 insertions(+), 53 deletions(-)
arch/x86/kvm/vmx/main.c +54 -0
index eeb7a43b271d..18cecf12c7c8 100644
@@ -167,3 +167,57 @@ struct kvm_x86_init_ops vt_init_ops __initdata = {
 	.runtime_ops = &vt_x86_ops,
 	.pmu_ops = &intel_pmu_ops,
 };
+
+static int __init vt_init(void)
+{
+	unsigned int vcpu_size, vcpu_align;
+	int cpu, r;
+
+	if (!kvm_is_vmx_supported())
+		return -EOPNOTSUPP;
+
+	/*
+	 * Note, hv_init_evmcs() touches only VMX knobs, i.e. there's nothing
+	 * to unwind if a later step fails.
+	 */
+	hv_init_evmcs();
+
+	/* vmx_hardware_disable() accesses loaded_vmcss_on_cpu. */
+	for_each_possible_cpu(cpu)
+		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
+
+	r = kvm_x86_vendor_init(&vt_init_ops);
+	if (r)
+		return r;
+
+	r = vmx_init();
+	if (r)
+		goto err_vmx_init;
+
+	/*
+	 * Common KVM initialization _must_ come last, after this, /dev/kvm is
+	 * exposed to userspace!
+	 */
+	vcpu_size = sizeof(struct vcpu_vmx);
+	vcpu_align = __alignof__(struct vcpu_vmx);
+	r = kvm_init(vcpu_size, vcpu_align, THIS_MODULE);
+	if (r)
+		goto err_kvm_init;
+
+	return 0;
+
+err_kvm_init:
+	vmx_exit();
+err_vmx_init:
+	kvm_x86_vendor_exit();
+	return r;
+}
+module_init(vt_init);
+
+static void vt_exit(void)
+{
+	kvm_exit();
+	kvm_x86_vendor_exit();
+	vmx_exit();
+}
+module_exit(vt_exit);
arch/x86/kvm/vmx/vmx.c +7 -53

116 lines not on this page · raw message

arch/x86/kvm/vmx/x86_ops.h +14 -0
index 2f8b6c43fe0f..b936388853ab 100644
@@ -6,6 +6,20 @@
 
 #include "x86.h"
 
+#if IS_ENABLED(CONFIG_HYPERV)
+__init void hv_init_evmcs(void);
+void hv_reset_evmcs(void);
+#else /* IS_ENABLED(CONFIG_HYPERV) */
+static inline __init void hv_init_evmcs(void) {}
+static inline void hv_reset_evmcs(void) {}
+#endif /* IS_ENABLED(CONFIG_HYPERV) */
+
+DECLARE_PER_CPU(struct list_head, loaded_vmcss_on_cpu);
+
+bool kvm_is_vmx_supported(void);
+int __init vmx_init(void);
+void vmx_exit(void);
+
 extern struct kvm_x86_ops vt_x86_ops __initdata;
 extern struct kvm_x86_init_ops vt_init_ops __initdata;
 
Signature · 1 line
2.25.1
Reviewed-by on v19 22/130
#95 lore raw

Re: [PATCH v19 022/130] KVM: x86/vmx: Refactor KVM VMX module init/exit functions

Quoted 36 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Currently, KVM VMX module initialization/exit functions are a single function each. Refactor KVM VMX module initialization functions into KVM common part and VMX part so that TDX specific part can be added cleanly. Opportunistically refactor module exit function as well.

The current module initialization flow is,
0.) Check if VMX is supported,
1.) hyper-v specific initialization,
2.) system-wide x86 specific and vendor specific initialization, 3.) Final VMX specific system-wide initialization,
4.) calculate the sizes of VMX kvm structure and VMX vcpu structure, 5.) report those sizes to the KVM common layer and KVM common
initialization

Refactor the KVM VMX module initialization function into functions with a wrapper function to separate VMX logic in vmx.c from a file, main.c, common among VMX and TDX. Introduce a wrapper function for vmx_init().

The KVM architecture common layer allocates struct kvm with reported size for architecture-specific code. The KVM VMX module defines its structure as struct vmx_kvm { struct kvm; VMX specific members;} and uses it as struct vmx kvm. Similar for vcpu structure. TDX KVM patches will define TDX specific kvm and vcpu structures.

The current module exit function is also a single function, a combination of VMX specific logic and common KVM logic. Refactor it into VMX specific logic and KVM common logic. This is just refactoring to keep the VMX specific logic in vmx.c from main.c.

---
v19:
- Eliminate the unnecessary churn with vmx_hardware_setup() by Xiaoyao

Quoted 6 lines

v18:
- Move loaded_vmcss_on_cpu initialization to vt_init() before kvm_x86_vendor_init().
- added __init to an empty stub fucntion, hv_init_evmcs().

With one minor comment. See below.

Quoted 156 lines
---
  arch/x86/kvm/vmx/main.c    | 54 ++++++++++++++++++++++++++++++++++
  arch/x86/kvm/vmx/vmx.c     | 60 +++++---------------------------------
  arch/x86/kvm/vmx/x86_ops.h | 14 +++++++++
  3 files changed, 75 insertions(+), 53 deletions(-)
arch/x86/kvm/vmx/main.c +54 -0
index eeb7a43b271d..18cecf12c7c8 100644
@@ -167,3 +167,57 @@ struct kvm_x86_init_ops vt_init_ops __initdata = {
  	.runtime_ops = &vt_x86_ops,
  	.pmu_ops = &intel_pmu_ops,
  };
+
+static int __init vt_init(void)
+{
+	unsigned int vcpu_size, vcpu_align;
+	int cpu, r;
+
+	if (!kvm_is_vmx_supported())
+		return -EOPNOTSUPP;
+
+	/*
+	 * Note, hv_init_evmcs() touches only VMX knobs, i.e. there's nothing
+	 * to unwind if a later step fails.
+	 */
+	hv_init_evmcs();
+
+	/* vmx_hardware_disable() accesses loaded_vmcss_on_cpu. */
+	for_each_possible_cpu(cpu)
+		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
+
+	r = kvm_x86_vendor_init(&vt_init_ops);
+	if (r)
+		return r;
+
+	r = vmx_init();
+	if (r)
+		goto err_vmx_init;
+
+	/*
+	 * Common KVM initialization _must_ come last, after this, /dev/kvm is
+	 * exposed to userspace!
+	 */
+	vcpu_size = sizeof(struct vcpu_vmx);
+	vcpu_align = __alignof__(struct vcpu_vmx);
+	r = kvm_init(vcpu_size, vcpu_align, THIS_MODULE);
+	if (r)
+		goto err_kvm_init;
+
+	return 0;
+
+err_kvm_init:
+	vmx_exit();
+err_vmx_init:
+	kvm_x86_vendor_exit();
+	return r;
+}
+module_init(vt_init);
+
+static void vt_exit(void)
+{
+	kvm_exit();
+	kvm_x86_vendor_exit();
+	vmx_exit();
+}
+module_exit(vt_exit);
arch/x86/kvm/vmx/vmx.c +6 -29
index 8af0668e4dca..2fb1cd2e28a2 100644
@@ -477,7 +477,7 @@ DEFINE_PER_CPU(struct vmcs *, current_vmcs);
   * We maintain a per-CPU linked-list of VMCS loaded on that CPU. This is needed
   * when a CPU is brought down, and we need to VMCLEAR all VMCSs loaded on it.
   */
-static DEFINE_PER_CPU(struct list_head, loaded_vmcss_on_cpu);
+DEFINE_PER_CPU(struct list_head, loaded_vmcss_on_cpu);
 
  static DECLARE_BITMAP(vmx_vpid_bitmap, VMX_NR_VPIDS);
  static DEFINE_SPINLOCK(vmx_vpid_lock);
@@ -537,7 +537,7 @@ static int hv_enable_l2_tlb_flush(struct kvm_vcpu *vcpu)
  	return 0;
  }
 
-static __init void hv_init_evmcs(void)
+__init void hv_init_evmcs(void)
  {
  	int cpu;
 
@@ -573,7 +573,7 @@ static __init void hv_init_evmcs(void)
  	}
  }
 
-static void hv_reset_evmcs(void)
+void hv_reset_evmcs(void)
  {
  	struct hv_vp_assist_page *vp_ap;
 
@@ -597,10 +597,6 @@ static void hv_reset_evmcs(void)
  	vp_ap->current_nested_vmcs = 0;
  	vp_ap->enlighten_vmentry = 0;
  }
-
-#else /* IS_ENABLED(CONFIG_HYPERV) */
-static void hv_init_evmcs(void) {}
-static void hv_reset_evmcs(void) {}
  #endif /* IS_ENABLED(CONFIG_HYPERV) */
 
  /*
@@ -2743,7 +2739,7 @@ static bool __kvm_is_vmx_supported(void)
  	return true;
  }
 
-static bool kvm_is_vmx_supported(void)
+bool kvm_is_vmx_supported(void)
  {
  	bool supported;
 
@@ -8508,7 +8504,7 @@ static void vmx_cleanup_l1d_flush(void)
  	l1tf_vmx_mitigation = VMENTER_L1D_FLUSH_AUTO;
  }
 
-static void __vmx_exit(void)
+void vmx_exit(void)
  {
  	allow_smaller_maxphyaddr = false;
 
@@ -8517,36 +8513,10 @@ static void __vmx_exit(void)
  	vmx_cleanup_l1d_flush();
  }
 
-static void vmx_exit(void)
-{
-	kvm_exit();
-	kvm_x86_vendor_exit();
-
-	__vmx_exit();
-}
-module_exit(vmx_exit);
-
-static int __init vmx_init(void)
+int __init vmx_init(void)
  {
  	int r, cpu;
 
-	if (!kvm_is_vmx_supported())
-		return -EOPNOTSUPP;
-
-	/*
-	 * Note, hv_init_evmcs() touches only VMX knobs, i.e. there's nothing
-	 * to unwind if a later step fails.
-	 */
-	hv_init_evmcs();
-
-	/* vmx_hardware_disable() accesses loaded_vmcss_on_cpu. */

arch/x86/kvm/vmx/vmx.c static void __vmx_exit(void) · line 8522

Quoted 10 lines
arch/x86/kvm/vmx/vmx.c +0 -7
-	for_each_possible_cpu(cpu)
-		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
-
-	r = kvm_x86_vendor_init(&vt_init_ops);
-	if (r)
-		return r;
-
  	/*
  	 * Must be called after common x86 init so enable_ept is properly set
  	 * up. Hand the parameter mitigation value in which was stored in

I am wondering whether the first sentence of above comment should be moved to vt_init()? So vt_init() has whole information about the init sequence.

Regards
Yin, Fengwei

Quoted 56 lines
arch/x86/kvm/vmx/vmx.c +1 -17
@@ -8556,7 +8526,7 @@ static int __init vmx_init(void)
  	 */
  	r = vmx_setup_l1d_flush(vmentry_l1d_flush_param);
  	if (r)
-		goto err_l1d_flush;
+		return r;
 
  	for_each_possible_cpu(cpu)
  		pi_init_cpu(cpu);
@@ -8573,21 +8543,5 @@ static int __init vmx_init(void)
  	if (!enable_ept)
  		allow_smaller_maxphyaddr = true;
 
-	/*
-	 * Common KVM initialization _must_ come last, after this, /dev/kvm is
-	 * exposed to userspace!
-	 */
-	r = kvm_init(sizeof(struct vcpu_vmx), __alignof__(struct vcpu_vmx),
-		     THIS_MODULE);
-	if (r)
-		goto err_kvm_init;
-
  	return 0;
-
-err_kvm_init:
-	__vmx_exit();
-err_l1d_flush:
-	kvm_x86_vendor_exit();
-	return r;
  }
-module_init(vmx_init);
arch/x86/kvm/vmx/x86_ops.h +14 -0
index 2f8b6c43fe0f..b936388853ab 100644
@@ -6,6 +6,20 @@
 
  #include "x86.h"
 
+#if IS_ENABLED(CONFIG_HYPERV)
+__init void hv_init_evmcs(void);
+void hv_reset_evmcs(void);
+#else /* IS_ENABLED(CONFIG_HYPERV) */
+static inline __init void hv_init_evmcs(void) {}
+static inline void hv_reset_evmcs(void) {}
+#endif /* IS_ENABLED(CONFIG_HYPERV) */
+
+DECLARE_PER_CPU(struct list_head, loaded_vmcss_on_cpu);
+
+bool kvm_is_vmx_supported(void);
+int __init vmx_init(void);
+void vmx_exit(void);
+
  extern struct kvm_x86_ops vt_x86_ops __initdata;
  extern struct kvm_x86_init_ops vt_init_ops __initdata;
 
#96 lore raw

Re: [PATCH v19 022/130] KVM: x86/vmx: Refactor KVM VMX module init/exit functions

Quoted 198 lines · Fengwei
Quoted 36 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Currently, KVM VMX module initialization/exit functions are a single function each. Refactor KVM VMX module initialization functions into KVM common part and VMX part so that TDX specific part can be added cleanly. Opportunistically refactor module exit function as well.

The current module initialization flow is,
0.) Check if VMX is supported,
1.) hyper-v specific initialization,
2.) system-wide x86 specific and vendor specific initialization, 3.) Final VMX specific system-wide initialization,
4.) calculate the sizes of VMX kvm structure and VMX vcpu structure, 5.) report those sizes to the KVM common layer and KVM common
initialization

Refactor the KVM VMX module initialization function into functions with a wrapper function to separate VMX logic in vmx.c from a file, main.c, common among VMX and TDX. Introduce a wrapper function for vmx_init().

The KVM architecture common layer allocates struct kvm with reported size for architecture-specific code. The KVM VMX module defines its structure as struct vmx_kvm { struct kvm; VMX specific members;} and uses it as struct vmx kvm. Similar for vcpu structure. TDX KVM patches will define TDX specific kvm and vcpu structures.

The current module exit function is also a single function, a combination of VMX specific logic and common KVM logic. Refactor it into VMX specific logic and KVM common logic. This is just refactoring to keep the VMX specific logic in vmx.c from main.c.

---
v19:
- Eliminate the unnecessary churn with vmx_hardware_setup() by Xiaoyao

Quoted 6 lines

v18:
- Move loaded_vmcss_on_cpu initialization to vt_init() before kvm_x86_vendor_init().
- added __init to an empty stub fucntion, hv_init_evmcs().

With one minor comment. See below.

Quoted 149 lines
---
  arch/x86/kvm/vmx/main.c    | 54 ++++++++++++++++++++++++++++++++++
  arch/x86/kvm/vmx/vmx.c     | 60 +++++---------------------------------
  arch/x86/kvm/vmx/x86_ops.h | 14 +++++++++
  3 files changed, 75 insertions(+), 53 deletions(-)
arch/x86/kvm/vmx/main.c +54 -0
index eeb7a43b271d..18cecf12c7c8 100644
@@ -167,3 +167,57 @@ struct kvm_x86_init_ops vt_init_ops __initdata = {
  	.runtime_ops = &vt_x86_ops,
  	.pmu_ops = &intel_pmu_ops,
  };
+
+static int __init vt_init(void)
+{
+	unsigned int vcpu_size, vcpu_align;
+	int cpu, r;
+
+	if (!kvm_is_vmx_supported())
+		return -EOPNOTSUPP;
+
+	/*
+	 * Note, hv_init_evmcs() touches only VMX knobs, i.e. there's nothing
+	 * to unwind if a later step fails.
+	 */
+	hv_init_evmcs();
+
+	/* vmx_hardware_disable() accesses loaded_vmcss_on_cpu. */
+	for_each_possible_cpu(cpu)
+		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
+
+	r = kvm_x86_vendor_init(&vt_init_ops);
+	if (r)
+		return r;
+
+	r = vmx_init();
+	if (r)
+		goto err_vmx_init;
+
+	/*
+	 * Common KVM initialization _must_ come last, after this, /dev/kvm is
+	 * exposed to userspace!
+	 */
+	vcpu_size = sizeof(struct vcpu_vmx);
+	vcpu_align = __alignof__(struct vcpu_vmx);
+	r = kvm_init(vcpu_size, vcpu_align, THIS_MODULE);
+	if (r)
+		goto err_kvm_init;
+
+	return 0;
+
+err_kvm_init:
+	vmx_exit();
+err_vmx_init:
+	kvm_x86_vendor_exit();
+	return r;
+}
+module_init(vt_init);
+
+static void vt_exit(void)
+{
+	kvm_exit();
+	kvm_x86_vendor_exit();
+	vmx_exit();
+}
+module_exit(vt_exit);
arch/x86/kvm/vmx/vmx.c +6 -34
index 8af0668e4dca..2fb1cd2e28a2 100644
@@ -477,7 +477,7 @@ DEFINE_PER_CPU(struct vmcs *, current_vmcs);
   * We maintain a per-CPU linked-list of VMCS loaded on that CPU. This is needed
   * when a CPU is brought down, and we need to VMCLEAR all VMCSs loaded on it.
   */
-static DEFINE_PER_CPU(struct list_head, loaded_vmcss_on_cpu);
+DEFINE_PER_CPU(struct list_head, loaded_vmcss_on_cpu);
  static DECLARE_BITMAP(vmx_vpid_bitmap, VMX_NR_VPIDS);
  static DEFINE_SPINLOCK(vmx_vpid_lock);
@@ -537,7 +537,7 @@ static int hv_enable_l2_tlb_flush(struct kvm_vcpu *vcpu)
  	return 0;
  }
-static __init void hv_init_evmcs(void)
+__init void hv_init_evmcs(void)
  {
  	int cpu;
@@ -573,7 +573,7 @@ static __init void hv_init_evmcs(void)
  	}
  }
-static void hv_reset_evmcs(void)
+void hv_reset_evmcs(void)
  {
  	struct hv_vp_assist_page *vp_ap;
@@ -597,10 +597,6 @@ static void hv_reset_evmcs(void)
  	vp_ap->current_nested_vmcs = 0;
  	vp_ap->enlighten_vmentry = 0;
  }
-
-#else /* IS_ENABLED(CONFIG_HYPERV) */
-static void hv_init_evmcs(void) {}
-static void hv_reset_evmcs(void) {}
  #endif /* IS_ENABLED(CONFIG_HYPERV) */
  /*
@@ -2743,7 +2739,7 @@ static bool __kvm_is_vmx_supported(void)
  	return true;
  }
-static bool kvm_is_vmx_supported(void)
+bool kvm_is_vmx_supported(void)
  {
  	bool supported;
@@ -8508,7 +8504,7 @@ static void vmx_cleanup_l1d_flush(void)
  	l1tf_vmx_mitigation = VMENTER_L1D_FLUSH_AUTO;
  }
-static void __vmx_exit(void)
+void vmx_exit(void)
  {
  	allow_smaller_maxphyaddr = false;
@@ -8517,36 +8513,10 @@ static void __vmx_exit(void)
  	vmx_cleanup_l1d_flush();
  }
-static void vmx_exit(void)
-{
-	kvm_exit();
-	kvm_x86_vendor_exit();
-
-	__vmx_exit();
-}
-module_exit(vmx_exit);
-
-static int __init vmx_init(void)
+int __init vmx_init(void)
  {
  	int r, cpu;
-	if (!kvm_is_vmx_supported())
-		return -EOPNOTSUPP;
-
-	/*
-	 * Note, hv_init_evmcs() touches only VMX knobs, i.e. there's nothing
-	 * to unwind if a later step fails.
-	 */
-	hv_init_evmcs();
-
-	/* vmx_hardware_disable() accesses loaded_vmcss_on_cpu. */
-	for_each_possible_cpu(cpu)
-		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
-
-	r = kvm_x86_vendor_init(&vt_init_ops);
-	if (r)
Quoted 8 lines

arch/x86/kvm/vmx/vmx.c static void __vmx_exit(void) · line 8522

Quoted 5 lines
arch/x86/kvm/vmx/vmx.c +0 -2
-		return r;
-
  	/*
  	 * Must be called after common x86 init so enable_ept is properly set
  	 * up. Hand the parameter mitigation value in which was stored in

I am wondering whether the first sentence of above comment should be moved to vt_init()? So vt_init() has whole information about the init sequence.

If we do so, we should move the call of "vmx_setup_l1d_flush() to vt_init(). I hesitated to remove static of vmx_setup_l1d_flush().

Signature · 1 line
Isaku Yamahata <[email protected]>
#97 lore raw

Re: [PATCH v19 022/130] KVM: x86/vmx: Refactor KVM VMX module init/exit functions

Quoted 32 lines · Isaku Yamahata
Quoted 24 lines
Quoted 24 lines
arch/x86/kvm/vmx/vmx.c +1 -21
-
-	__vmx_exit();
-}
-module_exit(vmx_exit);
-
-static int __init vmx_init(void)
+int __init vmx_init(void)
  {
  	int r, cpu;
-	if (!kvm_is_vmx_supported())
-		return -EOPNOTSUPP;
-
-	/*
-	 * Note, hv_init_evmcs() touches only VMX knobs, i.e. there's nothing
-	 * to unwind if a later step fails.
-	 */
-	hv_init_evmcs();
-
-	/* vmx_hardware_disable() accesses loaded_vmcss_on_cpu. */
-	for_each_possible_cpu(cpu)
-		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
-
-	r = kvm_x86_vendor_init(&vt_init_ops);
-	if (r)
Quoted 8 lines

arch/x86/kvm/vmx/vmx.c static void __vmx_exit(void) · line 8522

Quoted 5 lines
arch/x86/kvm/vmx/vmx.c +0 -2
-		return r;
-
  	/*
  	 * Must be called after common x86 init so enable_ept is properly set
  	 * up. Hand the parameter mitigation value in which was stored in

I am wondering whether the first sentence of above comment should be moved to vt_init()? So vt_init() has whole information about the init sequence.

Quoted 2 lines

If we do so, we should move the call of "vmx_setup_l1d_flush() to vt_init(). I hesitated to remove static of vmx_setup_l1d_flush().

I meant this one:
"Must be called after common x86 init so enable_ept is properly set up"

Not necessary to move vmx_setup_l1d_flush().

Regards
Yin, Fengwei

Quoted 1 line
#98 lore raw

Re: [PATCH v19 022/130] KVM: x86/vmx: Refactor KVM VMX module init/exit functions

Quoted 35 lines · Yin Fengwei
Quoted 32 lines · Isaku Yamahata
Quoted 32 lines
Quoted 19 lines
arch/x86/kvm/vmx/vmx.c +1 -16
-
-	__vmx_exit();
-}
-module_exit(vmx_exit);
-
-static int __init vmx_init(void)
+int __init vmx_init(void)
  {
  	int r, cpu;
-	if (!kvm_is_vmx_supported())
-		return -EOPNOTSUPP;
-
-	/*
-	 * Note, hv_init_evmcs() touches only VMX knobs, i.e. there's nothing
-	 * to unwind if a later step fails.
-	 */
-	hv_init_evmcs();
-
-	/* vmx_hardware_disable() accesses loaded_vmcss_on_cpu. */

arch/x86/kvm/vmx/vmx.c static void __vmx_exit(void) · line 8522

Quoted 10 lines
arch/x86/kvm/vmx/vmx.c +0 -7
-	for_each_possible_cpu(cpu)
-		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
-
-	r = kvm_x86_vendor_init(&vt_init_ops);
-	if (r)
-		return r;
-
  	/*
  	 * Must be called after common x86 init so enable_ept is properly set
  	 * up. Hand the parameter mitigation value in which was stored in

I am wondering whether the first sentence of above comment should be moved to vt_init()? So vt_init() has whole information about the init sequence.

Quoted 6 lines
Quoted 2 lines

If we do so, we should move the call of "vmx_setup_l1d_flush() to vt_init(). I hesitated to remove static of vmx_setup_l1d_flush().

I meant this one:
"Must be called after common x86 init so enable_ept is properly set up"

Not necessary to move vmx_setup_l1d_flush().

Ah, you mean "only" first sentence. Ok. I'll move it.

Signature · 1 line
Isaku Yamahata <[email protected]>
#99 lore raw

Re: [PATCH v19 022/130] KVM: x86/vmx: Refactor KVM VMX module init/exit functions

In reply to [email protected]
Quoted 8 lines · On Mon, 2024-02-26 at 00:25 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Currently, KVM VMX module initialization/exit functions are a single function each. Refactor KVM VMX module initialization functions into KVM common part and VMX part so that TDX specific part can be added cleanly. Opportunistically refactor module exit function as well.

The current module initialization flow is,

					  ^ ',' -> ':'

And please add an empty line to make text more breathable.

Quoted 7 lines

0.) Check if VMX is supported,
1.) hyper-v specific initialization,
2.) system-wide x86 specific and vendor specific initialization, 3.) Final VMX specific system-wide initialization,
4.) calculate the sizes of VMX kvm structure and VMX vcpu structure, 5.) report those sizes to the KVM common layer and KVM common
initialization

Is there any difference between "KVM common layer" and "KVM common initialization"? I think you can remove the former.

Quoted 4 lines

Refactor the KVM VMX module initialization function into functions with a wrapper function to separate VMX logic in vmx.c from a file, main.c, common among VMX and TDX. Introduce a wrapper function for vmx_init().

Sorry I don't quite follow what your are trying to say in the above paragraph.

You have adequately put what is the _current_ flow, and I am expecting to see the flow _after_ the refactor here.

Quoted 5 lines

The KVM architecture common layer allocates struct kvm with reported size for architecture-specific code. The KVM VMX module defines its structure as struct vmx_kvm { struct kvm; VMX specific members;} and uses it as struct vmx kvm. Similar for vcpu structure. TDX KVM patches will define

	 ^vmx_kvm.

Please be more consistent on the words.

Quoted 1 line

TDX specific kvm and vcpu structures.

Is this paragraph related to the changes in this patch?

For instance, why do you need to point out we will have TDX-specific 'kvm and vcpu' structures?

Quoted 5 lines

The current module exit function is also a single function, a combination of VMX specific logic and common KVM logic. Refactor it into VMX specific logic and KVM common logic.

[...]

Quoted 2 lines

This is just refactoring to keep the VMX
specific logic in vmx.c from main.c.

It's better to make this as a separate paragraph, because it is a summary to this patch.

And in other words: No functional change intended?

#100 lore raw

Re: [PATCH v19 022/130] KVM: x86/vmx: Refactor KVM VMX module init/exit functions

Quoted 22 lines · Kai
Quoted 8 lines · On Mon, 2024-02-26 at 00:25 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Currently, KVM VMX module initialization/exit functions are a single function each. Refactor KVM VMX module initialization functions into KVM common part and VMX part so that TDX specific part can be added cleanly. Opportunistically refactor module exit function as well.

The current module initialization flow is,

					  ^ ',' -> ':'

And please add an empty line to make text more breathable.

Quoted 7 lines

0.) Check if VMX is supported,
1.) hyper-v specific initialization,
2.) system-wide x86 specific and vendor specific initialization, 3.) Final VMX specific system-wide initialization,
4.) calculate the sizes of VMX kvm structure and VMX vcpu structure, 5.) report those sizes to the KVM common layer and KVM common
initialization

Quoted 2 lines

Is there any difference between "KVM common layer" and "KVM common initialization"? I think you can remove the former.

Ok.

Quoted 8 lines
Quoted 3 lines

Refactor the KVM VMX module initialization function into functions with a wrapper function to separate VMX logic in vmx.c from a file, main.c, common among VMX and TDX. Introduce a wrapper function for vmx_init().

Sorry I don't quite follow what your are trying to say in the above paragraph.

You have adequately put what is the _current_ flow, and I am expecting to see the flow _after_ the refactor here.

Will add it.

Quoted 7 lines
Quoted 4 lines

The KVM architecture common layer allocates struct kvm with reported size for architecture-specific code. The KVM VMX module defines its structure as struct vmx_kvm { struct kvm; VMX specific members;} and uses it as struct vmx kvm. Similar for vcpu structure. TDX KVM patches will define

	 ^vmx_kvm.
Quoted 8 lines

Please be more consistent on the words.

Quoted 1 line

TDX specific kvm and vcpu structures.

Is this paragraph related to the changes in this patch?

For instance, why do you need to point out we will have TDX-specific 'kvm and vcpu' structures?

The point of this refactoring is to make room for TDX-specific code. The consideration point is data size/alignment difference and VMX-dependency. Let me re-order the sentences.

Quoted 7 lines
Quoted 4 lines

The current module exit function is also a single function, a combination of VMX specific logic and common KVM logic. Refactor it into VMX specific logic and KVM common logic.

[...]

Quoted 7 lines
Quoted 2 lines

This is just refactoring to keep the VMX
specific logic in vmx.c from main.c.

It's better to make this as a separate paragraph, because it is a summary to this patch.

And in other words: No functional change intended?

Thanks for the feedback. Here is the revised version.

KVM: x86/vmx: Refactor KVM VMX module init/exit functions

Split KVM VMX kernel module initialization into one specific to VMX in vmx.c and one common for VMX and TDX in main.c to make room for TDX-specific logic to fit in. Opportunistically, refactor module exit function as well.

The key points are data structure difference and TDX dependency on VMX. The data structures for TDX are different from VMX. So are its size and alignment. Because TDX depends on VMX, TDX initialization must be after VMX initialization. TDX cleanup must be before VMX cleanup.

The current module initialization flow is:

0.) Check if VMX is supported,
1.) Hyper-v specific initialization,
2.) System-wide x86 specific and vendor-specific initialization, 3.) Final VMX-specific system-wide initialization,
4.) Calculate the sizes of the kvm and vcpu structure for VMX, 5.) Report those sizes to the KVM-common initialization

After refactoring and TDX, the flow will be:

0.) Check if VMX is supported (main.c),
1.) Hyper-v specific initialization (main.c),
2.) System-wide x86 specific and vendor-specific initialization,
2.1) VMX-specific initialization (vmx.c)
2.2) TDX-specific initialization (tdx.c)
3.) Final VMX-specific system-wide initialization (vmx.c),
TDX doesn't need this step.
4.) Calculate the sizes of the kvm and vcpu structure for both VMX and

    TDX, (main.c)

5.) Report those sizes to the KVM-common initialization (main.c)

No functional change intended.

Signature · 1 line
Isaku Yamahata <[email protected]>
#101 lore raw

PATCH v19 23/130 KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TDX requires several initialization steps for KVM to create guest TDs. Detect CPU feature, enable VMX (TDX is based on VMX) on all online CPUs, detect the TDX module availability, initialize it and disable VMX.

To enable/disable VMX on all online CPUs, utilize vmx_hardware_enable/disable(). The method also initializes each CPU for TDX. TDX requires calling a TDX initialization function per logical processor (LP) before the LP uses TDX. When the CPU is becoming online, call the TDX LP initialization API. If it fails to initialize TDX, refuse CPU online for simplicity instead of TDX avoiding the failed LP.

There are several options on when to initialize the TDX module. A.) kernel module loading time, B.) the first guest TD creation time. A.) was chosen. With B.), a user may hit an error of the TDX initialization when trying to create the first guest TD. The machine that fails to initialize the TDX module can't boot any guest TD further. Such failure is undesirable and a surprise because the user expects that the machine can accommodate guest TD, but not. So A.) is better than B.).

Introduce a module parameter, kvm_intel.tdx, to explicitly enable TDX KVM support. It's off by default to keep the same behavior for those who don't use TDX. Implement hardware_setup method to detect TDX feature of CPU and initialize TDX module.

---
v19:
- fixed vt_hardware_enable() to use vmx_hardware_enable()
- renamed vmx_tdx_enabled => tdx_enabled
- renamed vmx_tdx_on() => tdx_on()

v18:
- Added comment in vt_hardware_enable() by Binbin.

---
 arch/x86/kvm/Makefile      |  1 +
 arch/x86/kvm/vmx/main.c    | 19 ++++++++-
 arch/x86/kvm/vmx/tdx.c     | 84 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h |  6 +++
 4 files changed, 109 insertions(+), 1 deletion(-)
 create mode 100644 arch/x86/kvm/vmx/tdx.c
arch/x86/kvm/Makefile +1 -0
index 274df24b647f..5b85ef84b2e9 100644
@@ -24,6 +24,7 @@ kvm-intel-y		+= vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o vmx/vmcs12.o \
 
 kvm-intel-$(CONFIG_X86_SGX_KVM)	+= vmx/sgx.o
 kvm-intel-$(CONFIG_KVM_HYPERV)	+= vmx/hyperv.o vmx/hyperv_evmcs.o
+kvm-intel-$(CONFIG_INTEL_TDX_HOST)	+= vmx/tdx.o
 
 kvm-amd-y		+= svm/svm.o svm/vmenter.o svm/pmu.o svm/nested.o svm/avic.o \
 			   svm/sev.o
arch/x86/kvm/vmx/main.c +18 -1
index 18cecf12c7c8..18aef6e23aab 100644
@@ -6,6 +6,22 @@
 #include "nested.h"
 #include "pmu.h"
 
+static bool enable_tdx __ro_after_init;
+module_param_named(tdx, enable_tdx, bool, 0444);
+
+static __init int vt_hardware_setup(void)
+{
+	int ret;
+
+	ret = vmx_hardware_setup();
+	if (ret)
+		return ret;
+
+	enable_tdx = enable_tdx && !tdx_hardware_setup(&vt_x86_ops);
+
+	return 0;
+}
+
 #define VMX_REQUIRED_APICV_INHIBITS				\
 	(BIT(APICV_INHIBIT_REASON_DISABLE)|			\
 	 BIT(APICV_INHIBIT_REASON_ABSENT) |			\
@@ -22,6 +38,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.hardware_unsetup = vmx_hardware_unsetup,
 
+	/* TDX cpu enablement is done by tdx_hardware_setup(). */
 	.hardware_enable = vmx_hardware_enable,
 	.hardware_disable = vmx_hardware_disable,
 	.has_emulated_msr = vmx_has_emulated_msr,
@@ -161,7 +178,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 };
 
 struct kvm_x86_init_ops vt_init_ops __initdata = {
-	.hardware_setup = vmx_hardware_setup,
+	.hardware_setup = vt_hardware_setup,
 	.handle_intel_pt_intr = NULL,
 
 	.runtime_ops = &vt_x86_ops,
arch/x86/kvm/vmx/tdx.c new file +84 -0

84 lines not on this page · raw message

arch/x86/kvm/vmx/x86_ops.h +6 -0
index b936388853ab..346289a2a01c 100644
@@ -135,4 +135,10 @@ void vmx_cancel_hv_timer(struct kvm_vcpu *vcpu);
 #endif
 void vmx_setup_mce(struct kvm_vcpu *vcpu);
 
+#ifdef CONFIG_INTEL_TDX_HOST
+int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops);
+#else
+static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return -EOPNOTSUPP; }
+#endif
+
 #endif /* __KVM_X86_VMX_X86_OPS_H */
Signature · 1 line
2.25.1
#102 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 86 lines · On 2/26/2024 4:25 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX requires several initialization steps for KVM to create guest TDs. Detect CPU feature, enable VMX (TDX is based on VMX) on all online CPUs, detect the TDX module availability, initialize it and disable VMX.

To enable/disable VMX on all online CPUs, utilize vmx_hardware_enable/disable(). The method also initializes each CPU for TDX. TDX requires calling a TDX initialization function per logical processor (LP) before the LP uses TDX. When the CPU is becoming online, call the TDX LP initialization API. If it fails to initialize TDX, refuse CPU online for simplicity instead of TDX avoiding the failed LP.

There are several options on when to initialize the TDX module. A.) kernel module loading time, B.) the first guest TD creation time. A.) was chosen. With B.), a user may hit an error of the TDX initialization when trying to create the first guest TD. The machine that fails to initialize the TDX module can't boot any guest TD further. Such failure is undesirable and a surprise because the user expects that the machine can accommodate guest TD, but not. So A.) is better than B.).

Introduce a module parameter, kvm_intel.tdx, to explicitly enable TDX KVM support. It's off by default to keep the same behavior for those who don't use TDX. Implement hardware_setup method to detect TDX feature of CPU and initialize TDX module.

---
v19:
- fixed vt_hardware_enable() to use vmx_hardware_enable()
- renamed vmx_tdx_enabled => tdx_enabled
- renamed vmx_tdx_on() => tdx_on()

v18:
- Added comment in vt_hardware_enable() by Binbin.

---
  arch/x86/kvm/Makefile      |  1 +
  arch/x86/kvm/vmx/main.c    | 19 ++++++++-
  arch/x86/kvm/vmx/tdx.c     | 84 ++++++++++++++++++++++++++++++++++++++
  arch/x86/kvm/vmx/x86_ops.h |  6 +++
  4 files changed, 109 insertions(+), 1 deletion(-)

create mode 100644 arch/x86/kvm/vmx/tdx.c

arch/x86/kvm/Makefile +1 -0
index 274df24b647f..5b85ef84b2e9 100644
@@ -24,6 +24,7 @@ kvm-intel-y		+= vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o vmx/vmcs12.o \
 
  kvm-intel-$(CONFIG_X86_SGX_KVM)	+= vmx/sgx.o
  kvm-intel-$(CONFIG_KVM_HYPERV)	+= vmx/hyperv.o vmx/hyperv_evmcs.o
+kvm-intel-$(CONFIG_INTEL_TDX_HOST)	+= vmx/tdx.o
 
  kvm-amd-y		+= svm/svm.o svm/vmenter.o svm/pmu.o svm/nested.o svm/avic.o \
  			   svm/sev.o
arch/x86/kvm/vmx/main.c +16 -0
index 18cecf12c7c8..18aef6e23aab 100644
@@ -6,6 +6,22 @@
  #include "nested.h"
  #include "pmu.h"
 
+static bool enable_tdx __ro_after_init;
+module_param_named(tdx, enable_tdx, bool, 0444);
+
+static __init int vt_hardware_setup(void)
+{
+	int ret;
+
+	ret = vmx_hardware_setup();
+	if (ret)
+		return ret;
+
+	enable_tdx = enable_tdx && !tdx_hardware_setup(&vt_x86_ops);
+
+	return 0;
+}
+
  #define VMX_REQUIRED_APICV_INHIBITS				\
  	(BIT(APICV_INHIBIT_REASON_DISABLE)|			\
  	 BIT(APICV_INHIBIT_REASON_ABSENT) |			\

arch/x86/kvm/vmx/main.c struct kvm_x86_ops vt_x86_ops __initdata = { · line 41

Quoted 4 lines
arch/x86/kvm/vmx/main.c +1 -0
@@ -22,6 +38,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
  	.hardware_unsetup = vmx_hardware_unsetup,
 
+	/* TDX cpu enablement is done by tdx_hardware_setup(). */

How about if there are some LPs that are offline.
In tdx_hardware_setup(), only online LPs are initialed for TDX, right? Then when an offline LP becoming online, it doesn't have a chance to call tdx_cpu_enable()?

Quoted 117 lines
arch/x86/kvm/vmx/main.c +1 -1
  	.hardware_enable = vmx_hardware_enable,
  	.hardware_disable = vmx_hardware_disable,
  	.has_emulated_msr = vmx_has_emulated_msr,
@@ -161,7 +178,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
  };
 
  struct kvm_x86_init_ops vt_init_ops __initdata = {
-	.hardware_setup = vmx_hardware_setup,
+	.hardware_setup = vt_hardware_setup,
  	.handle_intel_pt_intr = NULL,
 
  	.runtime_ops = &vt_x86_ops,
arch/x86/kvm/vmx/tdx.c new file +84 -0
new file mode 100644
index 000000000000..43c504fb4fed
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/cpu.h>
+
+#include <asm/tdx.h>
+
+#include "capabilities.h"
+#include "x86_ops.h"
+#include "x86.h"
+
+#undef pr_fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+static int __init tdx_module_setup(void)
+{
+	int ret;
+
+	ret = tdx_enable();
+	if (ret) {
+		pr_info("Failed to initialize TDX module.\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+struct tdx_enabled {
+	cpumask_var_t enabled;
+	atomic_t err;
+};
+
+static void __init tdx_on(void *_enable)
+{
+	struct tdx_enabled *enable = _enable;
+	int r;
+
+	r = vmx_hardware_enable();
+	if (!r) {
+		cpumask_set_cpu(smp_processor_id(), enable->enabled);
+		r = tdx_cpu_enable();
+	}
+	if (r)
+		atomic_set(&enable->err, r);
+}
+
+static void __init vmx_off(void *_enabled)
+{
+	cpumask_var_t *enabled = (cpumask_var_t *)_enabled;
+
+	if (cpumask_test_cpu(smp_processor_id(), *enabled))
+		vmx_hardware_disable();
+}
+
+int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops)
+{
+	struct tdx_enabled enable = {
+		.err = ATOMIC_INIT(0),
+	};
+	int r = 0;
+
+	if (!enable_ept) {
+		pr_warn("Cannot enable TDX with EPT disabled\n");
+		return -EINVAL;
+	}
+
+	if (!zalloc_cpumask_var(&enable.enabled, GFP_KERNEL)) {
+		r = -ENOMEM;
+		goto out;
+	}
+
+	/* tdx_enable() in tdx_module_setup() requires cpus lock. */
+	cpus_read_lock();
+	on_each_cpu(tdx_on, &enable, true); /* TDX requires vmxon. */
+	r = atomic_read(&enable.err);
+	if (!r)
+		r = tdx_module_setup();
+	else
+		r = -EIO;
+	on_each_cpu(vmx_off, &enable.enabled, true);
+	cpus_read_unlock();
+	free_cpumask_var(enable.enabled);
+
+out:
+	return r;
+}
arch/x86/kvm/vmx/x86_ops.h +6 -0
index b936388853ab..346289a2a01c 100644
@@ -135,4 +135,10 @@ void vmx_cancel_hv_timer(struct kvm_vcpu *vcpu);
  #endif
  void vmx_setup_mce(struct kvm_vcpu *vcpu);
 
+#ifdef CONFIG_INTEL_TDX_HOST
+int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops);
+#else
+static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return -EOPNOTSUPP; }
+#endif
+
  #endif /* __KVM_X86_VMX_X86_OPS_H */
#103 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 24 lines · Binbin Wu
Quoted 24 lines
arch/x86/kvm/vmx/main.c +16 -0
index 18cecf12c7c8..18aef6e23aab 100644
@@ -6,6 +6,22 @@
  #include "nested.h"
  #include "pmu.h"
+static bool enable_tdx __ro_after_init;
+module_param_named(tdx, enable_tdx, bool, 0444);
+
+static __init int vt_hardware_setup(void)
+{
+	int ret;
+
+	ret = vmx_hardware_setup();
+	if (ret)
+		return ret;
+
+	enable_tdx = enable_tdx && !tdx_hardware_setup(&vt_x86_ops);
+
+	return 0;
+}
+
  #define VMX_REQUIRED_APICV_INHIBITS				\
Quoted 8 lines

arch/x86/kvm/vmx/main.c struct kvm_x86_ops vt_x86_ops __initdata = { · line 39

Quoted 5 lines
arch/x86/kvm/vmx/main.c +1 -0
  	(BIT(APICV_INHIBIT_REASON_DISABLE)|			\
  	 BIT(APICV_INHIBIT_REASON_ABSENT) |			\
@@ -22,6 +38,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
  	.hardware_unsetup = vmx_hardware_unsetup,
+	/* TDX cpu enablement is done by tdx_hardware_setup(). */

How about if there are some LPs that are offline.
In tdx_hardware_setup(), only online LPs are initialed for TDX, right?

Correct.

Quoted 2 lines

Then when an offline LP becoming online, it doesn't have a chance to call tdx_cpu_enable()?

KVM registers kvm_online/offline_cpu() @ kvm_main.c as cpu hotplug callbacks. Eventually x86 kvm hardware_enable() is called on online/offline event.

Signature · 1 line
Isaku Yamahata <[email protected]>
#104 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 34 lines · Isaku Yamahata
Quoted 23 lines · Binbin Wu
Quoted 23 lines
arch/x86/kvm/vmx/main.c +16 -0
index 18cecf12c7c8..18aef6e23aab 100644
@@ -6,6 +6,22 @@
   #include "nested.h"
   #include "pmu.h"
+static bool enable_tdx __ro_after_init;
+module_param_named(tdx, enable_tdx, bool, 0444);
+
+static __init int vt_hardware_setup(void)
+{
+	int ret;
+
+	ret = vmx_hardware_setup();
+	if (ret)
+		return ret;
+
+	enable_tdx = enable_tdx && !tdx_hardware_setup(&vt_x86_ops);
+
+	return 0;
+}
+
Quoted 8 lines

arch/x86/kvm/vmx/main.c struct kvm_x86_ops vt_x86_ops __initdata = { · line 39

Quoted 6 lines
arch/x86/kvm/vmx/main.c +1 -0
   #define VMX_REQUIRED_APICV_INHIBITS				\
   	(BIT(APICV_INHIBIT_REASON_DISABLE)|			\
   	 BIT(APICV_INHIBIT_REASON_ABSENT) |			\
@@ -22,6 +38,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
   	.hardware_unsetup = vmx_hardware_unsetup,
+	/* TDX cpu enablement is done by tdx_hardware_setup(). */

How about if there are some LPs that are offline.
In tdx_hardware_setup(), only online LPs are initialed for TDX, right?

Quoted 7 lines

Correct.

Quoted 2 lines

Then when an offline LP becoming online, it doesn't have a chance to call tdx_cpu_enable()?

KVM registers kvm_online/offline_cpu() @ kvm_main.c as cpu hotplug callbacks. Eventually x86 kvm hardware_enable() is called on online/offline event.

Yes, hardware_enable() will be called when online, but  hardware_enable() now is vmx_hardware_enable() right? It doens't call tdx_cpu_enable() during the online path.

#105 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 38 lines · Binbin Wu
Quoted 37 lines · Isaku Yamahata
Quoted 23 lines · Binbin Wu
Quoted 23 lines
arch/x86/kvm/vmx/main.c +16 -0
index 18cecf12c7c8..18aef6e23aab 100644
@@ -6,6 +6,22 @@
   #include "nested.h"
   #include "pmu.h"
+static bool enable_tdx __ro_after_init;
+module_param_named(tdx, enable_tdx, bool, 0444);
+
+static __init int vt_hardware_setup(void)
+{
+	int ret;
+
+	ret = vmx_hardware_setup();
+	if (ret)
+		return ret;
+
+	enable_tdx = enable_tdx && !tdx_hardware_setup(&vt_x86_ops);
+
+	return 0;
+}
+
Quoted 8 lines

arch/x86/kvm/vmx/main.c struct kvm_x86_ops vt_x86_ops __initdata = { · line 39

Quoted 6 lines
arch/x86/kvm/vmx/main.c +1 -0
   #define VMX_REQUIRED_APICV_INHIBITS				\
   	(BIT(APICV_INHIBIT_REASON_DISABLE)|			\
   	 BIT(APICV_INHIBIT_REASON_ABSENT) |			\
@@ -22,6 +38,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
   	.hardware_unsetup = vmx_hardware_unsetup,
+	/* TDX cpu enablement is done by tdx_hardware_setup(). */

How about if there are some LPs that are offline.
In tdx_hardware_setup(), only online LPs are initialed for TDX, right?

Correct.

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines

Then when an offline LP becoming online, it doesn't have a chance to call tdx_cpu_enable()?

KVM registers kvm_online/offline_cpu() @ kvm_main.c as cpu hotplug callbacks. Eventually x86 kvm hardware_enable() is called on online/offline event.

Yes, hardware_enable() will be called when online, but  hardware_enable() now is vmx_hardware_enable() right? It doens't call tdx_cpu_enable() during the online path.

TDX module requires TDH.SYS.LP.INIT() on all logical processors(LPs). If we successfully initialized TDX module, we don't need further action for TDX on cpu online/offline.

If some of LPs are not online when loading kvm_intel.ko, KVM fails to initialize TDX module. TDX support is disabled. We don't bother to attempt it. Leave it to the admin of the machine.

Signature · 1 line
Isaku Yamahata <[email protected]>
#106 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 14 lines · Isaku Yamahata
Quoted 5 lines
Quoted 5 lines
Quoted 2 lines

How about if there are some LPs that are offline.
In tdx_hardware_setup(), only online LPs are initialed for TDX, right?

Correct.

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines

Then when an offline LP becoming online, it doesn't have a chance to call tdx_cpu_enable()?

KVM registers kvm_online/offline_cpu() @ kvm_main.c as cpu hotplug callbacks. Eventually x86 kvm hardware_enable() is called on online/offline event.

Yes, hardware_enable() will be called when online, but  hardware_enable() now is vmx_hardware_enable() right? It doens't call tdx_cpu_enable() during the online path.

Quoted 7 lines

TDX module requires TDH.SYS.LP.INIT() on all logical processors(LPs).  If we successfully initialized TDX module, we don't need further action for TDX on cpu online/offline.

If some of LPs are not online when loading kvm_intel.ko, KVM fails to initialize TDX module. TDX support is disabled.  We don't bother to attempt it.  Leave it to the admin of the machine.

No. We have relaxed this. Now the TDX module can be initialized on a subset of all logical cpus, with arbitrary number of cpus being offline.

Those cpus can become online after module initialization, and TDH.SYS.LP.INIT on them won't fail.

#107 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 23 lines · Kai
Quoted 14 lines · Isaku Yamahata
Quoted 5 lines
Quoted 5 lines
Quoted 2 lines

How about if there are some LPs that are offline.
In tdx_hardware_setup(), only online LPs are initialed for TDX, right?

Correct.

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines

Then when an offline LP becoming online, it doesn't have a chance to call tdx_cpu_enable()?

KVM registers kvm_online/offline_cpu() @ kvm_main.c as cpu hotplug callbacks. Eventually x86 kvm hardware_enable() is called on online/offline event.

Yes, hardware_enable() will be called when online, but  hardware_enable() now is vmx_hardware_enable() right? It doens't call tdx_cpu_enable() during the online path.

Quoted 7 lines

TDX module requires TDH.SYS.LP.INIT() on all logical processors(LPs).  If we successfully initialized TDX module, we don't need further action for TDX on cpu online/offline.

If some of LPs are not online when loading kvm_intel.ko, KVM fails to initialize TDX module. TDX support is disabled.  We don't bother to attempt it.  Leave it to the admin of the machine.

Quoted 5 lines

No. We have relaxed this. Now the TDX module can be initialized on a subset of all logical cpus, with arbitrary number of cpus being offline.

Those cpus can become online after module initialization, and TDH.SYS.LP.INIT on them won't fail.

Ah, you're right. So we need to call tdx_cpu_enable() on online. For offline, KVM has to do nothing. It's another story to shutdown TDX module.

Signature · 1 line
Isaku Yamahata <[email protected]>
#108 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

In reply to [email protected]
Quoted 5 lines · On Mon, 2024-02-26 at 00:25 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX requires several initialization steps for KVM to create guest TDs. Detect CPU feature, enable VMX (TDX is based on VMX) on all online CPUs, detect the TDX module availability, initialize it and disable VMX.

Before KVM can use TDX to create and run TDX guests, the kernel needs to initialize TDX from two perspectives:

1) Initialize the TDX module.
1) Do the "per-cpu initialization" on any logical cpu before running any TDX code on that cpu.

The host kernel provides two functions to do them respectively: tdx_cpu_enable() and tdx_enable().

Currently, tdx_enable() requires all online cpus being in VMX operation with CPU hotplug disabled, and tdx_cpu_enable() needs to be called on local cpu with that cpu being in VMX operation and IRQ disabled.

Quoted 5 lines

To enable/disable VMX on all online CPUs, utilize vmx_hardware_enable/disable(). The method also initializes each CPU for TDX.

I don't understand what you are saying here.

Did you mean you put tdx_cpu_enable() inside vmx_hardware_enable()?

Quoted 3 lines

TDX requires calling a TDX initialization function per logical processor (LP) before the LP uses TDX.

[...]

Quoted 3 lines

When the CPU is becoming online,
call the TDX LP initialization API. If it fails to initialize TDX, refuse CPU online for simplicity instead of TDX avoiding the failed LP.

Unless I am missing something, I don't see this has been done in the code.

Quoted 3 lines

There are several options on when to initialize the TDX module. A.) kernel module loading time, B.) the first guest TD creation time. A.) was chosen.

A.) was chosen -> Choose A).

Describe your change in "imperative mood".

Quoted 7 lines

With B.), a user may hit an error of the TDX initialization when trying to create the first guest TD. The machine that fails to initialize the TDX module can't boot any guest TD further. Such failure is undesirable and a surprise because the user expects that the machine can accommodate guest TD, but not. So A.) is better than B.).

Introduce a module parameter, kvm_intel.tdx, to explicitly enable TDX KVM

You don't have to say the name of the new parameter. It's shown in the code.

Quoted 3 lines

support. It's off by default to keep the same behavior for those who don't use TDX.

[...]

Quoted 2 lines

Implement hardware_setup method to detect TDX feature of CPU and initialize TDX module.

You are not detecting TDX feature anymore.

And put this in a separate paragraph (at a better place), as I don't see how this is connected to "introduce a module parameter".

Quoted 61 lines

---
v19:
- fixed vt_hardware_enable() to use vmx_hardware_enable()
- renamed vmx_tdx_enabled => tdx_enabled
- renamed vmx_tdx_on() => tdx_on()

v18:
- Added comment in vt_hardware_enable() by Binbin.

---
 arch/x86/kvm/Makefile      |  1 +
 arch/x86/kvm/vmx/main.c    | 19 ++++++++-
 arch/x86/kvm/vmx/tdx.c     | 84 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h |  6 +++
 4 files changed, 109 insertions(+), 1 deletion(-)
 create mode 100644 arch/x86/kvm/vmx/tdx.c
arch/x86/kvm/Makefile +1 -0
index 274df24b647f..5b85ef84b2e9 100644
@@ -24,6 +24,7 @@ kvm-intel-y		+= vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o vmx/vmcs12.o \
 
 kvm-intel-$(CONFIG_X86_SGX_KVM)	+= vmx/sgx.o
 kvm-intel-$(CONFIG_KVM_HYPERV)	+= vmx/hyperv.o vmx/hyperv_evmcs.o
+kvm-intel-$(CONFIG_INTEL_TDX_HOST)	+= vmx/tdx.o
 
 kvm-amd-y		+= svm/svm.o svm/vmenter.o svm/pmu.o svm/nested.o svm/avic.o \
 			   svm/sev.o
arch/x86/kvm/vmx/main.c +16 -0
index 18cecf12c7c8..18aef6e23aab 100644
@@ -6,6 +6,22 @@
 #include "nested.h"
 #include "pmu.h"
 
+static bool enable_tdx __ro_after_init;
+module_param_named(tdx, enable_tdx, bool, 0444);
+
+static __init int vt_hardware_setup(void)
+{
+	int ret;
+
+	ret = vmx_hardware_setup();
+	if (ret)
+		return ret;
+
+	enable_tdx = enable_tdx && !tdx_hardware_setup(&vt_x86_ops);
+
+	return 0;
+}
+
 #define VMX_REQUIRED_APICV_INHIBITS				\
 	(BIT(APICV_INHIBIT_REASON_DISABLE)|			\
 	 BIT(APICV_INHIBIT_REASON_ABSENT) |			\

arch/x86/kvm/vmx/main.c struct kvm_x86_ops vt_x86_ops __initdata = { · line 41

Quoted 4 lines
arch/x86/kvm/vmx/main.c +1 -0
@@ -22,6 +38,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.hardware_unsetup = vmx_hardware_unsetup,
 
+	/* TDX cpu enablement is done by tdx_hardware_setup(). */

What's the point of this comment? I don't understand it either.

arch/x86/kvm/vmx/main.c struct kvm_x86_ops vt_x86_ops __initdata = { · line 43

Quoted 2 lines
arch/x86/kvm/vmx/main.c +0 -0
 	.hardware_enable = vmx_hardware_enable,
 	.hardware_disable = vmx_hardware_disable,

Shouldn't you also implement vt_hardware_enable(), which also does tdx_cpu_enable()?

Because I don't see vmx_hardware_enable() is changed to call tdx_cpu_enable() to make CPU hotplug work with TDX.

Quoted 25 lines
arch/x86/kvm/vmx/main.c +1 -1
 	.has_emulated_msr = vmx_has_emulated_msr,
@@ -161,7 +178,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 };
 
 struct kvm_x86_init_ops vt_init_ops __initdata = {
-	.hardware_setup = vmx_hardware_setup,
+	.hardware_setup = vt_hardware_setup,
 	.handle_intel_pt_intr = NULL,
 
 	.runtime_ops = &vt_x86_ops,
arch/x86/kvm/vmx/tdx.c new file +9 -0
new file mode 100644
index 000000000000..43c504fb4fed
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/cpu.h>
+
+#include <asm/tdx.h>
+
+#include "capabilities.h"
+#include "x86_ops.h"
+#include "x86.h"
+

arch/x86/kvm/vmx/tdx.c line 19

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c new file +10 -0
+#undef pr_fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+static int __init tdx_module_setup(void)
+{
+	int ret;
+
+	ret = tdx_enable();
+	if (ret) {
+		pr_info("Failed to initialize TDX module.\n");

As I commented before, tdx_enable() itself will print similar message when it fails, so no need to print again.

arch/x86/kvm/vmx/tdx.c line 24

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c new file +5 -0
+		return ret;
+	}
+
+	return 0;
+}

That being said, I don't think tdx_module_setup() is necessary. Just call tdx_enable() directly.

arch/x86/kvm/vmx/tdx.c line 29

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c new file +5 -0
+
+struct tdx_enabled {
+	cpumask_var_t enabled;
+	atomic_t err;
+};
struct cpu_tdx_init_ctx {
	cpumask_var_t vmx_enabled_cpumask;
	atomic_t err;
};

?

arch/x86/kvm/vmx/tdx.c line 31

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c new file +2 -0
+
+static void __init tdx_on(void *_enable)

tdx_on() -> cpu_tdx_init(), or cpu_tdx_on()?

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c new file +4 -0
+{
+	struct tdx_enabled *enable = _enable;
+	int r;
+

arch/x86/kvm/vmx/tdx.c line 45

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c new file +10 -0
+	r = vmx_hardware_enable();
+	if (!r) {
+		cpumask_set_cpu(smp_processor_id(), enable->enabled);
+		r = tdx_cpu_enable();
+	}
+	if (r)
+		atomic_set(&enable->err, r);
+}
+
+static void __init vmx_off(void *_enabled)

cpu_vmx_off() ?

arch/x86/kvm/vmx/tdx.c line 53

Quoted 8 lines
arch/x86/kvm/vmx/tdx.c new file +8 -0
+{
+	cpumask_var_t *enabled = (cpumask_var_t *)_enabled;
+
+	if (cpumask_test_cpu(smp_processor_id(), *enabled))
+		vmx_hardware_disable();
+}
+
+int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops)

Why do you need the 'x86_ops' function argument? I don't see it is used?

Quoted 7 lines
arch/x86/kvm/vmx/tdx.c new file +7 -0
+{
+	struct tdx_enabled enable = {
+		.err = ATOMIC_INIT(0),
+	};
+	int r = 0;
+
+	if (!enable_ept) {

arch/x86/kvm/vmx/tdx.c line 70

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c new file +10 -0
+		pr_warn("Cannot enable TDX with EPT disabled\n");
+		return -EINVAL;
+	}
+
+	if (!zalloc_cpumask_var(&enable.enabled, GFP_KERNEL)) {
+		r = -ENOMEM;
+		goto out;
+	}
+
+	/* tdx_enable() in tdx_module_setup() requires cpus lock. */
	/* tdx_enable() must be called with CPU hotplug disabled */

arch/x86/kvm/vmx/tdx.c line 72

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c new file +2 -0
+	cpus_read_lock();
+	on_each_cpu(tdx_on, &enable, true); /* TDX requires vmxon. */

I don't think you need this comment _here_.

If you want keep it, move to the tdx_on() where the code does what this comment say.

arch/x86/kvm/vmx/tdx.c line 84

Quoted 12 lines
arch/x86/kvm/vmx/tdx.c new file +12 -0
+	r = atomic_read(&enable.err);
+	if (!r)
+		r = tdx_module_setup();
+	else
+		r = -EIO;
+	on_each_cpu(vmx_off, &enable.enabled, true);
+	cpus_read_unlock();
+	free_cpumask_var(enable.enabled);
+
+out:
+	return r;
+}

At last, I think there's one problem here:

KVM actually only registers CPU hotplug callback in kvm_init(), which happens way after tdx_hardware_setup().

What happens if any CPU goes online *BETWEEN* tdx_hardware_setup() and kvm_init()?

Looks we have two options:

1) move registering CPU hotplug callback before tdx_hardware_setup(), or
2) we need to disable CPU hotplug until callbacks have been registered.

Perhaps the second one is easier, because for the first one we need to make sure the kvm_cpu_online() is ready to be called right after tdx_hardware_setup().

And no one cares if CPU hotplug is disabled during KVM module loading.

That being said, we can even just disable CPU hotplug during the entire vt_init(), if in this way the code change is simple?

But anyway, to make this patch complete, I think you need to replace vmx_hardware_enable() to vt_hardware_enable() and do tdx_cpu_enable() to handle TDX vs CPU hotplug in _this_ patch.

#109 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 22 lines · Kai
Quoted 5 lines · On Mon, 2024-02-26 at 00:25 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX requires several initialization steps for KVM to create guest TDs. Detect CPU feature, enable VMX (TDX is based on VMX) on all online CPUs, detect the TDX module availability, initialize it and disable VMX.

Before KVM can use TDX to create and run TDX guests, the kernel needs to initialize TDX from two perspectives:

1) Initialize the TDX module.
1) Do the "per-cpu initialization" on any logical cpu before running any TDX code on that cpu.

The host kernel provides two functions to do them respectively: tdx_cpu_enable() and tdx_enable().

Currently, tdx_enable() requires all online cpus being in VMX operation with CPU hotplug disabled, and tdx_cpu_enable() needs to be called on local cpu with that cpu being in VMX operation and IRQ disabled.

Quoted 1 line
Quoted 8 lines
Quoted 4 lines

To enable/disable VMX on all online CPUs, utilize vmx_hardware_enable/disable(). The method also initializes each CPU for TDX.

I don't understand what you are saying here.

Did you mean you put tdx_cpu_enable() inside vmx_hardware_enable()?

Now the section doesn't make sense. Will remove it.

Quoted 11 lines
Quoted 3 lines

TDX requires calling a TDX initialization function per logical processor (LP) before the LP uses TDX.

[...]

Quoted 3 lines

When the CPU is becoming online,
call the TDX LP initialization API. If it fails to initialize TDX, refuse CPU online for simplicity instead of TDX avoiding the failed LP.

Unless I am missing something, I don't see this has been done in the code.

You're right. Somehow the code was lost. Let me revive it with the next version.

Quoted 24 lines
Quoted 2 lines

There are several options on when to initialize the TDX module. A.) kernel module loading time, B.) the first guest TD creation time. A.) was chosen.

A.) was chosen -> Choose A).

Describe your change in "imperative mood".

Quoted 7 lines

With B.), a user may hit an error of the TDX initialization when trying to create the first guest TD. The machine that fails to initialize the TDX module can't boot any guest TD further. Such failure is undesirable and a surprise because the user expects that the machine can accommodate guest TD, but not. So A.) is better than B.).

Introduce a module parameter, kvm_intel.tdx, to explicitly enable TDX KVM

You don't have to say the name of the new parameter. It's shown in the code.

Quoted 3 lines

support. It's off by default to keep the same behavior for those who don't use TDX.

[...]

Quoted 7 lines
Quoted 2 lines

Implement hardware_setup method to detect TDX feature of CPU and initialize TDX module.

You are not detecting TDX feature anymore.

And put this in a separate paragraph (at a better place), as I don't see how this is connected to "introduce a module parameter".

Let me update those sentences.

Quoted 61 lines
Quoted 61 lines

---
v19:
- fixed vt_hardware_enable() to use vmx_hardware_enable()
- renamed vmx_tdx_enabled => tdx_enabled
- renamed vmx_tdx_on() => tdx_on()

v18:
- Added comment in vt_hardware_enable() by Binbin.

---
 arch/x86/kvm/Makefile      |  1 +
 arch/x86/kvm/vmx/main.c    | 19 ++++++++-
 arch/x86/kvm/vmx/tdx.c     | 84 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h |  6 +++
 4 files changed, 109 insertions(+), 1 deletion(-)
 create mode 100644 arch/x86/kvm/vmx/tdx.c
arch/x86/kvm/Makefile +1 -0
index 274df24b647f..5b85ef84b2e9 100644
@@ -24,6 +24,7 @@ kvm-intel-y		+= vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o vmx/vmcs12.o \
 
 kvm-intel-$(CONFIG_X86_SGX_KVM)	+= vmx/sgx.o
 kvm-intel-$(CONFIG_KVM_HYPERV)	+= vmx/hyperv.o vmx/hyperv_evmcs.o
+kvm-intel-$(CONFIG_INTEL_TDX_HOST)	+= vmx/tdx.o
 
 kvm-amd-y		+= svm/svm.o svm/vmenter.o svm/pmu.o svm/nested.o svm/avic.o \
 			   svm/sev.o
arch/x86/kvm/vmx/main.c +16 -0
index 18cecf12c7c8..18aef6e23aab 100644
@@ -6,6 +6,22 @@
 #include "nested.h"
 #include "pmu.h"
 
+static bool enable_tdx __ro_after_init;
+module_param_named(tdx, enable_tdx, bool, 0444);
+
+static __init int vt_hardware_setup(void)
+{
+	int ret;
+
+	ret = vmx_hardware_setup();
+	if (ret)
+		return ret;
+
+	enable_tdx = enable_tdx && !tdx_hardware_setup(&vt_x86_ops);
+
+	return 0;
+}
+
 #define VMX_REQUIRED_APICV_INHIBITS				\
 	(BIT(APICV_INHIBIT_REASON_DISABLE)|			\
 	 BIT(APICV_INHIBIT_REASON_ABSENT) |			\
@@ -22,6 +38,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
Quoted 5 lines

arch/x86/kvm/vmx/main.c struct kvm_x86_ops vt_x86_ops __initdata = { · line 41

Quoted 3 lines
arch/x86/kvm/vmx/main.c +1 -0
 	.hardware_unsetup = vmx_hardware_unsetup,
 
+	/* TDX cpu enablement is done by tdx_hardware_setup(). */

What's the point of this comment? I don't understand it either.

Will delete the comment.

Quoted 8 lines

arch/x86/kvm/vmx/main.c struct kvm_x86_ops vt_x86_ops __initdata = { · line 43

Quoted 2 lines
arch/x86/kvm/vmx/main.c +0 -0
 	.hardware_enable = vmx_hardware_enable,
 	.hardware_disable = vmx_hardware_disable,

Shouldn't you also implement vt_hardware_enable(), which also does tdx_cpu_enable()?

Because I don't see vmx_hardware_enable() is changed to call tdx_cpu_enable() to make CPU hotplug work with TDX.

hardware_enable() doesn't help for cpu hot plug support. See below.

Quoted 39 lines
Quoted 25 lines
arch/x86/kvm/vmx/main.c +1 -1
 	.has_emulated_msr = vmx_has_emulated_msr,
@@ -161,7 +178,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 };
 
 struct kvm_x86_init_ops vt_init_ops __initdata = {
-	.hardware_setup = vmx_hardware_setup,
+	.hardware_setup = vt_hardware_setup,
 	.handle_intel_pt_intr = NULL,
 
 	.runtime_ops = &vt_x86_ops,
arch/x86/kvm/vmx/tdx.c new file +9 -0
new file mode 100644
index 000000000000..43c504fb4fed
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/cpu.h>
+
+#include <asm/tdx.h>
+
+#include "capabilities.h"
+#include "x86_ops.h"
+#include "x86.h"
+

arch/x86/kvm/vmx/tdx.c line 19

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c new file +10 -0
+#undef pr_fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+static int __init tdx_module_setup(void)
+{
+	int ret;
+
+	ret = tdx_enable();
+	if (ret) {
+		pr_info("Failed to initialize TDX module.\n");

As I commented before, tdx_enable() itself will print similar message when it fails, so no need to print again.

Quoted 8 lines

arch/x86/kvm/vmx/tdx.c line 24

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c new file +5 -0
+		return ret;
+	}
+
+	return 0;
+}

That being said, I don't think tdx_module_setup() is necessary. Just call tdx_enable() directly.

Ok, Will move this funciton to the patch that uses it first.

Quoted 18 lines

arch/x86/kvm/vmx/tdx.c line 29

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c new file +5 -0
+
+struct tdx_enabled {
+	cpumask_var_t enabled;
+	atomic_t err;
+};
struct cpu_tdx_init_ctx {
	cpumask_var_t vmx_enabled_cpumask;
	atomic_t err;
};

?

arch/x86/kvm/vmx/tdx.c line 31

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c new file +2 -0
+
+static void __init tdx_on(void *_enable)

tdx_on() -> cpu_tdx_init(), or cpu_tdx_on()?

Quoted 16 lines
Quoted 4 lines
arch/x86/kvm/vmx/tdx.c new file +4 -0
+{
+	struct tdx_enabled *enable = _enable;
+	int r;
+

arch/x86/kvm/vmx/tdx.c line 45

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c new file +10 -0
+	r = vmx_hardware_enable();
+	if (!r) {
+		cpumask_set_cpu(smp_processor_id(), enable->enabled);
+		r = tdx_cpu_enable();
+	}
+	if (r)
+		atomic_set(&enable->err, r);
+}
+
+static void __init vmx_off(void *_enabled)

cpu_vmx_off() ?

Ok, let's add cpu_ prefix.

Quoted 10 lines

arch/x86/kvm/vmx/tdx.c line 53

Quoted 8 lines
arch/x86/kvm/vmx/tdx.c new file +8 -0
+{
+	cpumask_var_t *enabled = (cpumask_var_t *)_enabled;
+
+	if (cpumask_test_cpu(smp_processor_id(), *enabled))
+		vmx_hardware_disable();
+}
+
+int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops)

Why do you need the 'x86_ops' function argument? I don't see it is used?

Will move it to the patch that uses it first.

Quoted 20 lines
Quoted 7 lines
arch/x86/kvm/vmx/tdx.c new file +7 -0
+{
+	struct tdx_enabled enable = {
+		.err = ATOMIC_INIT(0),
+	};
+	int r = 0;
+
+	if (!enable_ept) {

arch/x86/kvm/vmx/tdx.c line 70

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c new file +10 -0
+		pr_warn("Cannot enable TDX with EPT disabled\n");
+		return -EINVAL;
+	}
+
+	if (!zalloc_cpumask_var(&enable.enabled, GFP_KERNEL)) {
+		r = -ENOMEM;
+		goto out;
+	}
+
+	/* tdx_enable() in tdx_module_setup() requires cpus lock. */
	/* tdx_enable() must be called with CPU hotplug disabled */
Quoted 7 lines

arch/x86/kvm/vmx/tdx.c line 72

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c new file +2 -0
+	cpus_read_lock();
+	on_each_cpu(tdx_on, &enable, true); /* TDX requires vmxon. */

I don't think you need this comment _here_.

If you want keep it, move to the tdx_on() where the code does what this comment say.

Will move the comment into cpu_tdx_on().

Quoted 29 lines

arch/x86/kvm/vmx/tdx.c line 84

Quoted 12 lines
arch/x86/kvm/vmx/tdx.c new file +12 -0
+	r = atomic_read(&enable.err);
+	if (!r)
+		r = tdx_module_setup();
+	else
+		r = -EIO;
+	on_each_cpu(vmx_off, &enable.enabled, true);
+	cpus_read_unlock();
+	free_cpumask_var(enable.enabled);
+
+out:
+	return r;
+}

At last, I think there's one problem here:

KVM actually only registers CPU hotplug callback in kvm_init(), which happens way after tdx_hardware_setup().

What happens if any CPU goes online *BETWEEN* tdx_hardware_setup() and kvm_init()?

Looks we have two options:

1) move registering CPU hotplug callback before tdx_hardware_setup(), or
2) we need to disable CPU hotplug until callbacks have been registered.

Perhaps the second one is easier, because for the first one we need to make sure the kvm_cpu_online() is ready to be called right after tdx_hardware_setup().

Quoted 8 lines

And no one cares if CPU hotplug is disabled during KVM module loading.

That being said, we can even just disable CPU hotplug during the entire vt_init(), if in this way the code change is simple?

But anyway, to make this patch complete, I think you need to replace vmx_hardware_enable() to vt_hardware_enable() and do tdx_cpu_enable() to handle TDX vs CPU hotplug in _this_ patch.

The option 2 sounds easier. But hardware_enable() doesn't help because it's called when the first guest is created. It's risky to change it's semantics because it's arch-independent callback.

- Disable CPU hot plug during TDX module initialization.
- During hardware_setup(), enable VMX, tdx_cpu_enable(), disable VMX on online cpu. Don't rely on KVM hooks.
- Add a new arch-independent hook, int kvm_arch_online_cpu(). It's called always on cpu onlining. It eventually calls tdx_cpu_enabel(). If it fails, refuse onlining.

Signature · 1 line
Isaku Yamahata <[email protected]>
#110 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 38 lines · Isaku Yamahata
Quoted 29 lines

arch/x86/kvm/vmx/tdx.c line 84

Quoted 12 lines
arch/x86/kvm/vmx/tdx.c new file +12 -0
+	r = atomic_read(&enable.err);
+	if (!r)
+		r = tdx_module_setup();
+	else
+		r = -EIO;
+	on_each_cpu(vmx_off, &enable.enabled, true);
+	cpus_read_unlock();
+	free_cpumask_var(enable.enabled);
+
+out:
+	return r;
+}

At last, I think there's one problem here:

KVM actually only registers CPU hotplug callback in kvm_init(), which happens way after tdx_hardware_setup().

What happens if any CPU goes online *BETWEEN* tdx_hardware_setup() and kvm_init()?

Looks we have two options:

1) move registering CPU hotplug callback before tdx_hardware_setup(), or
2) we need to disable CPU hotplug until callbacks have been registered.

Perhaps the second one is easier, because for the first one we need to make sure the kvm_cpu_online() is ready to be called right after tdx_hardware_setup().

Quoted 8 lines

And no one cares if CPU hotplug is disabled during KVM module loading.

That being said, we can even just disable CPU hotplug during the entire vt_init(), if in this way the code change is simple?

But anyway, to make this patch complete, I think you need to replace vmx_hardware_enable() to vt_hardware_enable() and do tdx_cpu_enable() to handle TDX vs CPU hotplug in _this_ patch.

Quoted 5 lines

The option 2 sounds easier. But hardware_enable() doesn't help because it's called when the first guest is created. It's risky to change it's semantics because it's arch-independent callback.

- Disable CPU hot plug during TDX module initialization.

As we talked, it turns out it is problematic to do so, because cpus_read_lock() is also called by some other internal functions like static_call_update(). If we take cpus_read_lock() for the entire vt_init() then we will have nested cpus_read_lock().

Quoted 5 lines

- During hardware_setup(), enable VMX, tdx_cpu_enable(), disable VMX on online cpu. Don't rely on KVM hooks.
- Add a new arch-independent hook, int kvm_arch_online_cpu(). It's called always on cpu onlining. It eventually calls tdx_cpu_enabel(). If it fails, refuse onlining.

So the purpose of kvm_arch_online_cpu() is to always do "VMXON + tdx_cpu_enable() + VMXOFF" _regardless_ of the kvm_usage_count, so that we can make sure that:

When TDX is enabled by KVM, all online cpus are TDX-capable (have done tdx_cpu_enable() successfully).

And the code will be like:

	static int kvm_online_cpu(unsigned int cpu)
	{
		mutex_lock(&kvm_lock);
		ret = kvm_arch_online_cpu(cpu);
		if (!ret && kvm_usage_count)
			ret = __hardware_enable_nolock();
		mutex_unlock(&kvm_lock);
	}

This will need another kvm_x86_ops->online_cpu() where we can implement the TDX specific "VMXON + tdx_cpu_enable() + VMXOFF":

	int kvm_arch_online_cpu(unsigned int cpu)
	{
		return static_call(kvm_x86_online_cpu)(cpu);
	}

Somehow I don't quite like this because: 1) it introduces a new kvm_x86_ops-

Quoted 1 line

online_cpu(); 2) it's a little bit silly to do "VMXON + tdx_cpu_enable() +

VMXOFF" just for TDX and then immediately do VMXON when there's KVM usage.

And IIUC, it will NOT work if kvm_online_cpu() happens when kvm_usage_count > 0:
VMXON has actually already been done on this cpu, so that the "VMXON" before tdx_cpu_enable() will fail. Probably this can be addressed somehow, but still doesn't seem nice.

So the above "option 1" doesn't seem right to me.

After thinking again, I think we have been too nervous about "losing CPU hotplug between tdx_enable() and kvm_init(), and when there's no KVM usage".

Instead, I think it's acceptable we don't do tdx_cpu_enable() for new CPU when it is hotplugged during the above two cases.

We just need to guarantee all online cpus are TDX capable "when there's real KVM usage, i.e., there's real VM running".

So "option 2":

I believe we just need to do tdx_cpu_enable() in vt_hardware_enable() after vmx_hardware_enable():

1) When the first VM is created, KVM will try to do tdx_cpu_enable() for those CPUs that becomes online after tdx_enable(), and if any tdx_cpu_enabled() fails, the VM will not be created. Otherwise, all online cpus are TDX-capable.

2) When there's real VM running, and when a new CPU can successfully become online, it must be TDX-capable.

Failure of tdx_cpu_enable() in 2) is obviously fine.

The consequence of failure to do tdx_cpu_enable() in 1) is that, besides VM cannot be created, there might be some online CPUs are not TDX-capable when TDX is marked as enabled.

It's fine from KVM's perspective, because literally no VM is running.

From host kernel's perspective, the only tricky thing is #MC handler. It tries to use SEAMCALL to read the faulty page's status to determine whether it is a TDX private page. If #MC happens on those non-TDX-capable cpus, then the SEAMCALL will fail. But that is also fine as we don't need a precise result anyway.

Option 3:

We want still to make sure our goal:

When TDX is enabled by KVM, all online cpus are TDX-capable.

For that, we can register an *additional* TDX specific CPU hotplug callback right after tdx_enable() to handle any CPU hotplug "between tdx_enable() and kvm_init(), and when there's no KVM usage".

Specifically, we can use dynamically allocated CPU hotplug state to avoid having to hard-code another KVM-TDX-specific CPU hotplug callback state:

	r = cpuhp_setup_state_nocalls(CPUHP_AP_KVM_DYN, "kvm/cpu/tdx:online",        
                                      tdx_online_cpu, NULL);

In tdx_online_cpu(), we do tdx_cpu_enable() if enable_tdx is true.

The problem is if tdx_online_cpu() happens when there's already KVM usage, the kvm_online_cpu() has already done VMXON. So tdx_online_cpu() will need to grab the @kvm_lock mutex and check @kvm_usage_count to determine whether to do VMXON before tdx_cpu_enable().

However both @kvm_lock mutex and @kvm_usage_count are in kvm.ko, and it's not nice to export such low level thing to kvm-intel.ko for TDX.

Option 4:

To avoid exporting @kvm_lock and @kvm_usage_count, we can still register the TDX-specific CPU hotplug callback, but choose to do "unconditional tdx_cpu_enable()" w/o doing VMXON in tdx_online_cpu(). If that fails due to VMXON hasn't been done, let it fail. This basically means:

When TDX is enabled, KVM can only online CPU when there's running VM.

To summarize:

I think "option 2" should be the best solution for now. It's easy to implement and yet has no real issue.

Any comments?

#111 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 19 lines · Kai Huang
Quoted 18 lines · Isaku Yamahata
Quoted 18 lines

Review

Quoted 12 lines
(quoted diff) +12 -0
+	r = atomic_read(&enable.err);
+	if (!r)
+		r = tdx_module_setup();
+	else
+		r = -EIO;
+	on_each_cpu(vmx_off, &enable.enabled, true);
+	cpus_read_unlock();
+	free_cpumask_var(enable.enabled);
+
+out:
+	return r;
+}

At last, I think there's one problem here:

KVM actually only registers CPU hotplug callback in kvm_init(), which happens way after tdx_hardware_setup().

Quoted 7 lines
Quoted 7 lines
Quoted 7 lines

What happens if any CPU goes online *BETWEEN* tdx_hardware_setup() and kvm_init()?

Looks we have two options:

1) move registering CPU hotplug callback before tdx_hardware_setup(), or
2) we need to disable CPU hotplug until callbacks have been registered.

This is all so dumb (not TDX, the current state of KVM). All of the hardware enabling crud is pointless complex inherited from misguided, decade old paranoia that led to the decision to enable VMX if and only if VMs are running. Enabling VMX doesn't make the system less secure, and the insane dances we are doing to do VMXON on-demand makes everything *more* fragile.

And all of this complexity really was driven by VMX, enabling virtualization for every other vendor, including AMD/SVM, is completely uninteresting. Forcing other architectures/vendors to take on yet more complexity doesn't make any sense.

Barely tested, and other architectures would need to be converted, but I don't see any obvious reasons why we can't simply enable virtualization when the module is loaded.

The diffstat pretty much says it all.

---
 Documentation/virt/kvm/locking.rst |   4 -
 arch/x86/include/asm/kvm_host.h    |   3 +
 arch/x86/kvm/svm/svm.c             |   5 +-
 arch/x86/kvm/vmx/vmx.c             |  18 ++-
 arch/x86/kvm/x86.c                 |  22 ++--
 include/linux/kvm_host.h           |   2 +
 virt/kvm/kvm_main.c                | 181 +++++++----------------------
 7 files changed, 67 insertions(+), 168 deletions(-)
Documentation/virt/kvm/locking.rst +0 -4
index 02880d5552d5..0d6eff13fd46 100644
@@ -227,10 +227,6 @@ time it will be set using the Dirty tracking mechanism described above.
 :Type:		mutex
 :Arch:		any
 :Protects:	- vm_list
-		- kvm_usage_count
-		- hardware virtualization enable/disable
-:Comment:	KVM also disables CPU hotplug via cpus_read_lock() during
-		enable/disable.
 
 ``kvm->mn_invalidate_lock``
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
arch/x86/include/asm/kvm_host.h +3 -0
index 73740d698ebe..7422239987d8 100644
@@ -36,6 +36,7 @@
 #include <asm/kvm_page_track.h>
 #include <asm/kvm_vcpu_regs.h>
 #include <asm/hyperv-tlfs.h>
+#include <asm/reboot.h>
 
 #define __KVM_HAVE_ARCH_VCPU_DEBUGFS
 
@@ -1605,6 +1606,8 @@ struct kvm_x86_ops {
 
 	int (*hardware_enable)(void);
 	void (*hardware_disable)(void);
+	cpu_emergency_virt_cb *emergency_disable;
+
 	void (*hardware_unsetup)(void);
 	bool (*has_emulated_msr)(struct kvm *kvm, u32 index);
 	void (*vcpu_after_set_cpuid)(struct kvm_vcpu *vcpu);
arch/x86/kvm/svm/svm.c +1 -4
index 9aaf83c8d57d..7e118284934c 100644
@@ -4917,6 +4917,7 @@ static void *svm_alloc_apic_backing_page(struct kvm_vcpu *vcpu)
 static struct kvm_x86_ops svm_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
+	.emergency_disable = svm_emergency_disable,
 	.check_processor_compatibility = svm_check_processor_compat,
 
 	.hardware_unsetup = svm_hardware_unsetup,
@@ -5348,8 +5349,6 @@ static struct kvm_x86_init_ops svm_init_ops __initdata = {
 static void __svm_exit(void)
 {
 	kvm_x86_vendor_exit();
-
-	cpu_emergency_unregister_virt_callback(svm_emergency_disable);
 }
 
 static int __init svm_init(void)
@@ -5365,8 +5364,6 @@ static int __init svm_init(void)
 	if (r)
 		return r;
 
-	cpu_emergency_register_virt_callback(svm_emergency_disable);
-
 	/*
 	 * Common KVM initialization _must_ come last, after this, /dev/kvm is
 	 * exposed to userspace!
arch/x86/kvm/vmx/vmx.c +8 -10
index d18dcb1e11a6..0dbe74da7ee3 100644
@@ -8320,6 +8320,8 @@ static struct kvm_x86_ops vmx_x86_ops __initdata = {
 
 	.hardware_enable = vmx_hardware_enable,
 	.hardware_disable = vmx_hardware_disable,
+	.emergency_disable = vmx_emergency_disable,
+
 	.has_emulated_msr = vmx_has_emulated_msr,
 
 	.vm_size = sizeof(struct kvm_vmx),
@@ -8733,8 +8735,6 @@ static void __vmx_exit(void)
 {
 	allow_smaller_maxphyaddr = false;
 
-	cpu_emergency_unregister_virt_callback(vmx_emergency_disable);
-
 	vmx_cleanup_l1d_flush();
 }
 
@@ -8760,6 +8760,12 @@ static int __init vmx_init(void)
 	 */
 	hv_init_evmcs();
 
+	for_each_possible_cpu(cpu) {
+		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
+
+		pi_init_cpu(cpu);
+	}
+
 	r = kvm_x86_vendor_init(&vmx_init_ops);
 	if (r)
 		return r;
@@ -8775,14 +8781,6 @@ static int __init vmx_init(void)
 	if (r)
 		goto err_l1d_flush;
 
-	for_each_possible_cpu(cpu) {
-		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
-
-		pi_init_cpu(cpu);
-	}
-
-	cpu_emergency_register_virt_callback(vmx_emergency_disable);
-
 	vmx_check_vmcs12_offsets();
 
 	/*
arch/x86/kvm/x86.c +11 -11
index 26288ca05364..41d3e4e32e20 100644
@@ -9687,15 +9687,10 @@ static int kvm_x86_check_processor_compatibility(void)
 	return static_call(kvm_x86_check_processor_compatibility)();
 }
 
-static void kvm_x86_check_cpu_compat(void *ret)
-{
-	*(int *)ret = kvm_x86_check_processor_compatibility();
-}
-
 int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
 {
 	u64 host_pat;
-	int r, cpu;
+	int r;
 
 	guard(mutex)(&vendor_module_lock);
 
@@ -9771,11 +9766,11 @@ int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
 
 	kvm_ops_update(ops);
 
-	for_each_online_cpu(cpu) {
-		smp_call_function_single(cpu, kvm_x86_check_cpu_compat, &r, 1);
-		if (r < 0)
-			goto out_unwind_ops;
-	}
+	cpu_emergency_register_virt_callback(kvm_x86_ops.emergency_disable);
+
+	r = kvm_enable_virtualization();
+	if (r)
+		goto out_unwind_ops;
 
 	/*
 	 * Point of no return!  DO NOT add error paths below this point unless
@@ -9818,6 +9813,7 @@ int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
 	return 0;
 
 out_unwind_ops:
+	cpu_emergency_unregister_virt_callback(kvm_x86_ops.emergency_disable);
 	kvm_x86_ops.hardware_enable = NULL;
 	static_call(kvm_x86_hardware_unsetup)();
 out_mmu_exit:
@@ -9858,6 +9854,10 @@ void kvm_x86_vendor_exit(void)
 	static_key_deferred_flush(&kvm_xen_enabled);
 	WARN_ON(static_branch_unlikely(&kvm_xen_enabled.key));
 #endif
+
+	kvm_disable_virtualization();
+	cpu_emergency_unregister_virt_callback(kvm_x86_ops.emergency_disable);
+
 	mutex_lock(&vendor_module_lock);
 	kvm_x86_ops.hardware_enable = NULL;
 	mutex_unlock(&vendor_module_lock);
include/linux/kvm_host.h +2 -0
index 48f31dcd318a..92da2eee7448 100644
@@ -1518,6 +1518,8 @@ static inline void kvm_create_vcpu_debugfs(struct kvm_vcpu *vcpu) {}
 #endif
 
 #ifdef CONFIG_KVM_GENERIC_HARDWARE_ENABLING
+int kvm_enable_virtualization(void);
+void kvm_disable_virtualization(void);
 int kvm_arch_hardware_enable(void);
 void kvm_arch_hardware_disable(void);
 #endif
virt/kvm/kvm_main.c +42 -139

292 lines not on this page · raw message

base-commit: f10f3621ad80f008c218dbbc13a05c893766a7d2

#112 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 34 lines · Sean Christopherson
Quoted 19 lines · Kai Huang
Quoted 18 lines · Isaku Yamahata
Quoted 18 lines

Review

Quoted 12 lines
(quoted diff) +12 -0
+	r = atomic_read(&enable.err);
+	if (!r)
+		r = tdx_module_setup();
+	else
+		r = -EIO;
+	on_each_cpu(vmx_off, &enable.enabled, true);
+	cpus_read_unlock();
+	free_cpumask_var(enable.enabled);
+
+out:
+	return r;
+}

At last, I think there's one problem here:

KVM actually only registers CPU hotplug callback in kvm_init(), which happens way after tdx_hardware_setup().

Quoted 7 lines
Quoted 7 lines
Quoted 7 lines

What happens if any CPU goes online *BETWEEN* tdx_hardware_setup() and kvm_init()?

Looks we have two options:

1) move registering CPU hotplug callback before tdx_hardware_setup(), or
2) we need to disable CPU hotplug until callbacks have been registered.

This is all so dumb (not TDX, the current state of KVM). All of the hardware enabling crud is pointless complex inherited from misguided, decade old paranoia that led to the decision to enable VMX if and only if VMs are running. Enabling VMX doesn't make the system less secure, and the insane dances we are doing to do VMXON on-demand makes everything *more* fragile.

Quoted 3 lines

And all of this complexity really was driven by VMX, enabling virtualization for every other vendor, including AMD/SVM, is completely uninteresting. Forcing other architectures/vendors to take on yet more complexity doesn't make any sense.

Ah, I actually preferred this solution, but I was trying to follow your suggestion here:

https://lore.kernel.org/lkml/[email protected]/

form which I interpreted you didn't like always having VMX enabled when KVM is present. :-)

Quoted 6 lines

Barely tested, and other architectures would need to be converted, but I don't see any obvious reasons why we can't simply enable virtualization when the module is loaded.

The diffstat pretty much says it all.

Thanks a lot for the code!

I can certainly follow up with this and generate a reviewable patchset if I can confirm with you that this is what you want?

#113 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 20 lines · Kai Huang
Quoted 15 lines · Sean Christopherson
Quoted 7 lines · Kai Huang
Quoted 7 lines
Quoted 7 lines

What happens if any CPU goes online *BETWEEN* tdx_hardware_setup() and kvm_init()?

Looks we have two options:

1) move registering CPU hotplug callback before tdx_hardware_setup(), or
2) we need to disable CPU hotplug until callbacks have been registered.

This is all so dumb (not TDX, the current state of KVM). All of the hardware enabling crud is pointless complex inherited from misguided, decade old paranoia that led to the decision to enable VMX if and only if VMs are running. Enabling VMX doesn't make the system less secure, and the insane dances we are doing to do VMXON on-demand makes everything *more* fragile.

Quoted 3 lines

And all of this complexity really was driven by VMX, enabling virtualization for every other vendor, including AMD/SVM, is completely uninteresting. Forcing other architectures/vendors to take on yet more complexity doesn't make any sense.

Quoted 7 lines

Ah, I actually preferred this solution, but I was trying to follow your suggestion here:

https://lore.kernel.org/lkml/[email protected]/

form which I interpreted you didn't like always having VMX enabled when KVM is present. :-)

I had a feeling I said something along those lines in the past.

Quoted 10 lines
Quoted 5 lines

Barely tested, and other architectures would need to be converted, but I don't see any obvious reasons why we can't simply enable virtualization when the module is loaded.

The diffstat pretty much says it all.

Thanks a lot for the code!

I can certainly follow up with this and generate a reviewable patchset if I can confirm with you that this is what you want?

Yes, I think it's the right direction. I still have minor concerns about VMX being enabled while kvm.ko is loaded, which means that VMXON will _always_ be enabled if KVM is built-in. But after seeing the complexity that is needed to safely initialize TDX, and after seeing just how much complexity KVM already has because it enables VMX on-demand (I hadn't actually tried removing that code before), I think the cost of that complexity far outweighs the risk of "always" being post-VMXON.

Within reason, I recommend getting feedback from others before you spend _too_ much time on this. It's entirely possible I'm missing/forgetting some other angle.

#114 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 42 lines · Sean Christopherson
Quoted 20 lines · Kai Huang
Quoted 15 lines · Sean Christopherson
Quoted 7 lines · Kai Huang
Quoted 7 lines
Quoted 7 lines

What happens if any CPU goes online *BETWEEN* tdx_hardware_setup() and kvm_init()?

Looks we have two options:

1) move registering CPU hotplug callback before tdx_hardware_setup(), or
2) we need to disable CPU hotplug until callbacks have been registered.

This is all so dumb (not TDX, the current state of KVM). All of the hardware enabling crud is pointless complex inherited from misguided, decade old paranoia that led to the decision to enable VMX if and only if VMs are running. Enabling VMX doesn't make the system less secure, and the insane dances we are doing to do VMXON on-demand makes everything *more* fragile.

Quoted 3 lines

And all of this complexity really was driven by VMX, enabling virtualization for every other vendor, including AMD/SVM, is completely uninteresting. Forcing other architectures/vendors to take on yet more complexity doesn't make any sense.

Quoted 7 lines

Ah, I actually preferred this solution, but I was trying to follow your suggestion here:

https://lore.kernel.org/lkml/[email protected]/

form which I interpreted you didn't like always having VMX enabled when KVM is present. :-)

I had a feeling I said something along those lines in the past.

Quoted 10 lines
Quoted 5 lines

Barely tested, and other architectures would need to be converted, but I don't see any obvious reasons why we can't simply enable virtualization when the module is loaded.

The diffstat pretty much says it all.

Thanks a lot for the code!

I can certainly follow up with this and generate a reviewable patchset if I can confirm with you that this is what you want?

Quoted 7 lines

Yes, I think it's the right direction. I still have minor concerns about VMX being enabled while kvm.ko is loaded, which means that VMXON will _always_ be enabled if KVM is built-in. But after seeing the complexity that is needed to safely initialize TDX, and after seeing just how much complexity KVM already has because it enables VMX on-demand (I hadn't actually tried removing that code before), I think the cost of that complexity far outweighs the risk of "always" being post-VMXON.

Does always leaving VMXON have any actual damage, given we have emergency virtualization shutdown?

Quoted 3 lines

Within reason, I recommend getting feedback from others before you spend _too_ much time on this. It's entirely possible I'm missing/forgetting some other angle.

Sure. Could you suggest who should we try to get feedback from?

Perhaps you can just help to Cc them?

Thanks for your time.

#115 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 13 lines · Kai Huang
Quoted 11 lines · Sean Christopherson
Quoted 2 lines · Kai Huang

I can certainly follow up with this and generate a reviewable patchset if I can confirm with you that this is what you want?

Yes, I think it's the right direction. I still have minor concerns about VMX being enabled while kvm.ko is loaded, which means that VMXON will _always_ be enabled if KVM is built-in. But after seeing the complexity that is needed to safely initialize TDX, and after seeing just how much complexity KVM already has because it enables VMX on-demand (I hadn't actually tried removing that code before), I think the cost of that complexity far outweighs the risk of "always" being post-VMXON.

Quoted 2 lines

Does always leaving VMXON have any actual damage, given we have emergency virtualization shutdown?

Being post-VMXON increases the risk of kexec() into the kdump kernel failing. The tradeoffs that we're trying to balance are: is the risk of kexec() failing due to the complexity of the emergency VMX code higher than the risk of us breaking things in general due to taking on a ton of complexity to juggle VMXON for TDX?

After seeing the latest round of TDX code, my opinion is that being post-VMXON is less risky overall, in no small part because we need that to work anyways for hosts that are actively running VMs.

Quoted 6 lines
Quoted 2 lines

Within reason, I recommend getting feedback from others before you spend _too_ much time on this. It's entirely possible I'm missing/forgetting some other angle.

Sure. Could you suggest who should we try to get feedback from?

Perhaps you can just help to Cc them?

I didn't have anyone in particular in mind, I just really want *someone* to weigh in as a sanity check.

#116 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 17 lines · Sean Christopherson
Quoted 13 lines · Kai Huang
Quoted 11 lines · Sean Christopherson
Quoted 2 lines · Kai Huang

I can certainly follow up with this and generate a reviewable patchset if I can confirm with you that this is what you want?

Yes, I think it's the right direction. I still have minor concerns about VMX being enabled while kvm.ko is loaded, which means that VMXON will _always_ be enabled if KVM is built-in. But after seeing the complexity that is needed to safely initialize TDX, and after seeing just how much complexity KVM already has because it enables VMX on-demand (I hadn't actually tried removing that code before), I think the cost of that complexity far outweighs the risk of "always" being post-VMXON.

Quoted 2 lines

Does always leaving VMXON have any actual damage, given we have emergency virtualization shutdown?

Quoted 10 lines

Being post-VMXON increases the risk of kexec() into the kdump kernel failing. The tradeoffs that we're trying to balance are: is the risk of kexec() failing due to the complexity of the emergency VMX code higher than the risk of us breaking things in general due to taking on a ton of complexity to juggle VMXON for TDX?

After seeing the latest round of TDX code, my opinion is that being post-VMXON is less risky overall, in no small part because we need that to work anyways for hosts that are actively running VMs.

Quoted 1 line

How about we only keep VMX always on when TDX is enabled?

In short, we can do this way:

- Do VMXON + unconditional tdx_cpu_enable() in vt_hardware_enable()

- And in vt_hardware_setup():

cpus_read_lock(); hardware_enable_all_nolock(); (this doesn't exist yet) ret = tdx_enable();
if (!ret)

	hardware_disable_all_nolock();

cpus_read_unlock();

- And in vt_hardware_unsetup():

   if (TDX is enabled) {
	cpus_read_lock();
	hardware_disable_all_nolock();
	cpus_read_unlock();

}

Note to make this work, we also need to move register/unregister kvm_online_cpu()/kvm_offline_cpu() from kvm_init() to hardware_enable_all_nolock() and hardware_disable_all_nolock() respectively to cover any CPU becoming online after tdx_enable() (well, more precisely, after hardware_enable_all_nolock()).

This is reasonable anyway even w/o TDX, because only _after_ we have enabled hardware on all online cpus, we need to handle CPU hotplug.

Calling hardware_enable_all_nolock() w/o holding kvm_lock mutex is also fine because at this stage it's not possible for userspace to create VM yet.

Btw, kvm_arch_hardware_enable() does things like TSC backworks, uret_msrs, etc but they are safe to be called during module load/unload AFAICT. We can put a comment there for reminder if needed.

If I am not missing anything, below diff to kvm.ko shows my idea:

(quoted diff) +0 -0

b/arch/x86/include/asm/kvm_host.h
index 16e07a2eee19..ed8b2f34af01 100644

arch/x86/include/asm/kvm_host.h +0 -0
@@ -2318,4 +2318,7 @@ int memslot_rmap_alloc(struct kvm_memory_slot *slot,

unsigned long npages);
*/
#define KVM_EXIT_HYPERCALL_MBZ GENMASK_ULL(31, 1)

+int hardware_enable_all_nolock(void);
+void hardware_disable_all_nolock(void);
+
#endif /* _ASM_X86_KVM_HOST_H */

virt/kvm/kvm_main.c +20 -3
index fb49c2a60200..3d2ff7dd0150 100644
@@ -5601,14 +5601,23 @@ static int kvm_offline_cpu(unsigned int cpu)
        return 0;
 }
 
-static void hardware_disable_all_nolock(void)
+static void __hardware_disable_all_nolock(void)
+{
+#ifdef CONFIG_KVM_GENERIC_HARDWARE_ENABLING
+       cpuhp_remove_state_nocalls(CPUHP_AP_KVM_ONLINE);
+#endif
+       on_each_cpu(hardware_disable_nolock, NULL, 1);
+}
+
+void hardware_disable_all_nolock(void)
 {
        BUG_ON(!kvm_usage_count);
 
        kvm_usage_count--;
        if (!kvm_usage_count)
-               on_each_cpu(hardware_disable_nolock, NULL, 1);
+               __hardware_disable_all_nolock();
 }
+EXPORT_SYMBOL_GPL(hardware_disable_all_nolock);
 
 static void hardware_disable_all(void)
 {
@@ -5619,11 +5628,27 @@ static void hardware_disable_all(void)
        cpus_read_unlock();
 }
 
-static int hardware_enable_all(void)
+static int __hardware_enable_all_nolock(void)
 {
        atomic_t failed = ATOMIC_INIT(0);
        int r;
 
+       on_each_cpu(hardware_enable_nolock, &failed, 1);
+
+       r = atomic_read(&failed);
+       if (r)
+               return -EBUSY;
+
+#ifdef CONFIG_KVM_GENERIC_HARDWARE_ENABLING
+       r = cpuhp_setup_state_nocalls(CPUHP_AP_KVM_ONLINE,

"kvm/cpu:online",
+ kvm_online_cpu, kvm_offline_cpu);
+#endif
+
+ return r;
+}
+
+int hardware_enable_all_nolock(void)
+{
/*
* Do not enable hardware virtualization if the system is going
down.
* If userspace initiated a forced reboot, e.g. reboot -f, then
it's
@@ -5637,6 +5662,24 @@ static int hardware_enable_all(void)

            system_state == SYSTEM_RESTART)
                return -EBUSY;

+ kvm_usage_count++;
+ if (kvm_usage_count == 1) {
+ int r = __hardware_enable_all_nolock();
+
+ if (r) {
+ hardware_disable_all_nolock();
+ return r;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hardware_enable_all_nolock);
+
+static int hardware_enable_all(void)
+{
+ int r;
+
/*
* When onlining a CPU, cpu_online_mask is set before
kvm_online_cpu()
* is called, and so on_each_cpu() between them includes the CPU
that
@@ -5648,17 +5691,7 @@ static int hardware_enable_all(void)

        cpus_read_lock();
        mutex_lock(&kvm_lock);

- r = 0;
-
- kvm_usage_count++;
- if (kvm_usage_count == 1) {
- on_each_cpu(hardware_enable_nolock, &failed, 1);
-
- if (atomic_read(&failed)) {
- hardware_disable_all_nolock();
- r = -EBUSY;
- }
- }
+ r = hardware_enable_all_nolock();

        mutex_unlock(&kvm_lock);
        cpus_read_unlock();
@@ -6422,11 +6455,6 @@ int kvm_init(unsigned vcpu_size, unsigned
vcpu_align, struct module *module)
        int cpu;

#ifdef CONFIG_KVM_GENERIC_HARDWARE_ENABLING
- r = cpuhp_setup_state_nocalls(CPUHP_AP_KVM_ONLINE,
"kvm/cpu:online",
- kvm_online_cpu, kvm_offline_cpu);
- if (r)
- return r;
-

        register_syscore_ops(&kvm_syscore_ops);

#endif

@@ -6528,7 +6556,6 @@ void kvm_exit(void)

        kvm_async_pf_deinit();
 #ifdef CONFIG_KVM_GENERIC_HARDWARE_ENABLING
        unregister_syscore_ops(&kvm_syscore_ops);
-       cpuhp_remove_state_nocalls(CPUHP_AP_KVM_ONLINE);
 #endif
        kvm_irqfd_exit();

}

#117 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 23 lines · Kai Huang
Quoted 22 lines · Sean Christopherson
Quoted 13 lines · Kai Huang
Quoted 11 lines · Sean Christopherson
Quoted 2 lines · Kai Huang

I can certainly follow up with this and generate a reviewable patchset if I can confirm with you that this is what you want?

Yes, I think it's the right direction. I still have minor concerns about VMX being enabled while kvm.ko is loaded, which means that VMXON will _always_ be enabled if KVM is built-in. But after seeing the complexity that is needed to safely initialize TDX, and after seeing just how much complexity KVM already has because it enables VMX on-demand (I hadn't actually tried removing that code before), I think the cost of that complexity far outweighs the risk of "always" being post-VMXON.

Quoted 2 lines

Does always leaving VMXON have any actual damage, given we have emergency virtualization shutdown?

Being post-VMXON increases the risk of kexec() into the kdump kernel failing. The tradeoffs that we're trying to balance are: is the risk of kexec() failing due to the complexity of the emergency VMX code higher than the risk of us breaking things in general due to taking on a ton of complexity to juggle VMXON for TDX?

Quoted 5 lines
Quoted 3 lines

After seeing the latest round of TDX code, my opinion is that being post-VMXON is less risky overall, in no small part because we need that to work anyways for hosts that are actively running VMs.

How about we only keep VMX always on when TDX is enabled?

Paolo also suggested that forcing VMXON only if TDX is enabled, mostly because kvm-intel.ko and kvm-amd.ko may be auto-loaded based on MODULE_DEVICE_TABLE(), which in turn causes problems for out-of-tree hypervisors that want control over VMX and SVM.

I'm not opposed to the idea, it's the complexity and messiness I dislike. E.g. the TDX code shouldn't have to deal with CPU hotplug locks, core KVM shouldn't need to expose nolock helpers, etc. And if we're going to make non-trivial changes to the core KVM hardware enabling code anyways...

What about this? Same basic idea as before, but instead of unconditionally doing hardware enabling during module initialization, let TDX do hardware enabling in a late_hardware_setup(), and then have KVM x86 ensure virtualization is enabled when creating VMs.

This way, architectures that aren't saddled with out-of-tree hypervisors can do the dead simple thing of enabling hardware during their initialization sequence, and the TDX code is much more sane, e.g. invoke kvm_x86_enable_virtualization() during late_hardware_setup(), and kvm_x86_disable_virtualization() during module exit (presumably).

---
 Documentation/virt/kvm/locking.rst |   4 -
 arch/x86/include/asm/kvm_host.h    |   3 +
 arch/x86/kvm/svm/svm.c             |   5 +-
 arch/x86/kvm/vmx/vmx.c             |  18 ++-
 arch/x86/kvm/x86.c                 |  59 +++++++---
 arch/x86/kvm/x86.h                 |   2 +
 include/linux/kvm_host.h           |   2 +
 virt/kvm/kvm_main.c                | 181 +++++++----------------------
 8 files changed, 104 insertions(+), 170 deletions(-)
Documentation/virt/kvm/locking.rst +0 -4
index 02880d5552d5..0d6eff13fd46 100644
@@ -227,10 +227,6 @@ time it will be set using the Dirty tracking mechanism described above.
 :Type:		mutex
 :Arch:		any
 :Protects:	- vm_list
-		- kvm_usage_count
-		- hardware virtualization enable/disable
-:Comment:	KVM also disables CPU hotplug via cpus_read_lock() during
-		enable/disable.
 
 ``kvm->mn_invalidate_lock``
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
arch/x86/include/asm/kvm_host.h +3 -0
index 73740d698ebe..7422239987d8 100644
@@ -36,6 +36,7 @@
 #include <asm/kvm_page_track.h>
 #include <asm/kvm_vcpu_regs.h>
 #include <asm/hyperv-tlfs.h>
+#include <asm/reboot.h>
 
 #define __KVM_HAVE_ARCH_VCPU_DEBUGFS
 
@@ -1605,6 +1606,8 @@ struct kvm_x86_ops {
 
 	int (*hardware_enable)(void);
 	void (*hardware_disable)(void);
+	cpu_emergency_virt_cb *emergency_disable;
+
 	void (*hardware_unsetup)(void);
 	bool (*has_emulated_msr)(struct kvm *kvm, u32 index);
 	void (*vcpu_after_set_cpuid)(struct kvm_vcpu *vcpu);
arch/x86/kvm/svm/svm.c +1 -4
index 9aaf83c8d57d..7e118284934c 100644
@@ -4917,6 +4917,7 @@ static void *svm_alloc_apic_backing_page(struct kvm_vcpu *vcpu)
 static struct kvm_x86_ops svm_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
+	.emergency_disable = svm_emergency_disable,
 	.check_processor_compatibility = svm_check_processor_compat,
 
 	.hardware_unsetup = svm_hardware_unsetup,
@@ -5348,8 +5349,6 @@ static struct kvm_x86_init_ops svm_init_ops __initdata = {
 static void __svm_exit(void)
 {
 	kvm_x86_vendor_exit();
-
-	cpu_emergency_unregister_virt_callback(svm_emergency_disable);
 }
 
 static int __init svm_init(void)
@@ -5365,8 +5364,6 @@ static int __init svm_init(void)
 	if (r)
 		return r;
 
-	cpu_emergency_register_virt_callback(svm_emergency_disable);
-
 	/*
 	 * Common KVM initialization _must_ come last, after this, /dev/kvm is
 	 * exposed to userspace!
arch/x86/kvm/vmx/vmx.c +8 -10
index d18dcb1e11a6..0dbe74da7ee3 100644
@@ -8320,6 +8320,8 @@ static struct kvm_x86_ops vmx_x86_ops __initdata = {
 
 	.hardware_enable = vmx_hardware_enable,
 	.hardware_disable = vmx_hardware_disable,
+	.emergency_disable = vmx_emergency_disable,
+
 	.has_emulated_msr = vmx_has_emulated_msr,
 
 	.vm_size = sizeof(struct kvm_vmx),
@@ -8733,8 +8735,6 @@ static void __vmx_exit(void)
 {
 	allow_smaller_maxphyaddr = false;
 
-	cpu_emergency_unregister_virt_callback(vmx_emergency_disable);
-
 	vmx_cleanup_l1d_flush();
 }
 
@@ -8760,6 +8760,12 @@ static int __init vmx_init(void)
 	 */
 	hv_init_evmcs();
 
+	for_each_possible_cpu(cpu) {
+		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
+
+		pi_init_cpu(cpu);
+	}
+
 	r = kvm_x86_vendor_init(&vmx_init_ops);
 	if (r)
 		return r;
@@ -8775,14 +8781,6 @@ static int __init vmx_init(void)
 	if (r)
 		goto err_l1d_flush;
 
-	for_each_possible_cpu(cpu) {
-		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
-
-		pi_init_cpu(cpu);
-	}
-
-	cpu_emergency_register_virt_callback(vmx_emergency_disable);
-
 	vmx_check_vmcs12_offsets();
 
 	/*
arch/x86/kvm/x86.c +46 -13

112 lines not on this page · raw message

arch/x86/kvm/x86.h +2 -0
index a8b71803777b..427c5d102525 100644
@@ -32,6 +32,8 @@ struct kvm_caps {
 };
 
 void kvm_spurious_fault(void);
+int kvm_x86_enable_virtualization(void);
+void kvm_x86_disable_virtualization(void);
 
 #define KVM_NESTED_VMENTER_CONSISTENCY_CHECK(consistency_check)		\
 ({									\
include/linux/kvm_host.h +2 -0
index 48f31dcd318a..92da2eee7448 100644
@@ -1518,6 +1518,8 @@ static inline void kvm_create_vcpu_debugfs(struct kvm_vcpu *vcpu) {}
 #endif
 
 #ifdef CONFIG_KVM_GENERIC_HARDWARE_ENABLING
+int kvm_enable_virtualization(void);
+void kvm_disable_virtualization(void);
 int kvm_arch_hardware_enable(void);
 void kvm_arch_hardware_disable(void);
 #endif
virt/kvm/kvm_main.c +42 -139

292 lines not on this page · raw message

base-commit: 2d181d84af38146748042a6974c577fc46c3f1c3

#118 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 45 lines · Sean Christopherson
Quoted 23 lines · Kai Huang
Quoted 22 lines · Sean Christopherson
Quoted 13 lines · Kai Huang
Quoted 11 lines · Sean Christopherson
Quoted 2 lines · Kai Huang

I can certainly follow up with this and generate a reviewable patchset if I can confirm with you that this is what you want?

Yes, I think it's the right direction. I still have minor concerns about VMX being enabled while kvm.ko is loaded, which means that VMXON will _always_ be enabled if KVM is built-in. But after seeing the complexity that is needed to safely initialize TDX, and after seeing just how much complexity KVM already has because it enables VMX on-demand (I hadn't actually tried removing that code before), I think the cost of that complexity far outweighs the risk of "always" being post-VMXON.

Quoted 2 lines

Does always leaving VMXON have any actual damage, given we have emergency virtualization shutdown?

Being post-VMXON increases the risk of kexec() into the kdump kernel failing. The tradeoffs that we're trying to balance are: is the risk of kexec() failing due to the complexity of the emergency VMX code higher than the risk of us breaking things in general due to taking on a ton of complexity to juggle VMXON for TDX?

Quoted 5 lines
Quoted 3 lines

After seeing the latest round of TDX code, my opinion is that being post-VMXON is less risky overall, in no small part because we need that to work anyways for hosts that are actively running VMs.

How about we only keep VMX always on when TDX is enabled?

Paolo also suggested that forcing VMXON only if TDX is enabled, mostly because kvm-intel.ko and kvm-amd.ko may be auto-loaded based on MODULE_DEVICE_TABLE(), which in turn causes problems for out-of-tree hypervisors that want control over VMX and SVM.

I'm not opposed to the idea, it's the complexity and messiness I dislike. E.g. the TDX code shouldn't have to deal with CPU hotplug locks, core KVM shouldn't need to expose nolock helpers, etc. And if we're going to make non-trivial changes to the core KVM hardware enabling code anyways...

What about this? Same basic idea as before, but instead of unconditionally doing hardware enabling during module initialization, let TDX do hardware enabling in a late_hardware_setup(), and then have KVM x86 ensure virtualization is enabled when creating VMs.

Quoted 5 lines

This way, architectures that aren't saddled with out-of-tree hypervisors can do the dead simple thing of enabling hardware during their initialization sequence, and the TDX code is much more sane, e.g. invoke kvm_x86_enable_virtualization() during late_hardware_setup(), and kvm_x86_disable_virtualization() during module exit (presumably).

Fine to me, given I am not familiar with other ARCHs, assuming always enable virtualization when KVM present is fine to them. :-)

Two questions below:

arch/x86/kvm/x86.c void kvm_arch_free_vm(struct kvm *kvm) · line 12530

Quoted 5 lines
arch/x86/kvm/x86.c +5 -0
+int kvm_x86_enable_virtualization(void)
+{
+	int r;
+
+	guard(mutex)(&vendor_module_lock);

It's a little bit odd to take the vendor_module_lock mutex.

It is called by kvm_arch_init_vm(), so more reasonablly we should still use kvm_lock?

Also, if we invoke kvm_x86_enable_virtualization() from kvm_x86_ops->late_hardware_setup(), then IIUC we will deadlock here because kvm_x86_vendor_init() already takes the vendor_module_lock?

Quoted 12 lines
arch/x86/kvm/x86.c +12 -0
+
+	if (kvm_usage_count++)
+		return 0;
+
+	r = kvm_enable_virtualization();
+	if (r)
+		--kvm_usage_count;
+
+	return r;
+}
+EXPORT_SYMBOL_GPL(kvm_x86_enable_virtualization);
+

[...]

Quoted 19 lines
virt/kvm/kvm_main.c +18 -0
+int kvm_enable_virtualization(void)
  {
+	int r;
+
+	r = cpuhp_setup_state(CPUHP_AP_KVM_ONLINE, "kvm/cpu:online",
+			      kvm_online_cpu, kvm_offline_cpu);
+	if (r)
+		return r;
+
+	register_syscore_ops(&kvm_syscore_ops);
+
+	/*
+	 * Manually undo virtualization enabling if the system is going down.
+	 * If userspace initiated a forced reboot, e.g. reboot -f, then it's
+	 * possible for an in-flight module load to enable virtualization
+	 * after syscore_shutdown() is called, i.e. without kvm_shutdown()
+	 * being invoked.  Note, this relies on system_state being set _before_
+	 * kvm_shutdown(), e.g. to ensure either kvm_shutdown() is invoked
+	 * or this CPU observes the impedning shutdown.  Which is why KVM uses

virt/kvm/kvm_main.c static struct syscore_ops kvm_syscore_ops = { · line 5639

Quoted 10 lines
virt/kvm/kvm_main.c +10 -0
+	 * a syscore ops hook instead of registering a dedicated reboot
+	 * notifier (the latter runs before system_state is updated).
+	 */
+	if (system_state == SYSTEM_HALT || system_state == SYSTEM_POWER_OFF ||
+	    system_state == SYSTEM_RESTART) {
+		unregister_syscore_ops(&kvm_syscore_ops);
+		cpuhp_remove_state(CPUHP_AP_KVM_ONLINE);
+		return -EBUSY;
+	}
+

Aren't we also supposed to do:

	on_each_cpu(__kvm_enable_virtualization, NULL, 1);

here?

Quoted 3 lines
  	return 0;
  }
#119 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 20 lines · Kai Huang
Quoted 5 lines · Sean Christopherson

This way, architectures that aren't saddled with out-of-tree hypervisors can do the dead simple thing of enabling hardware during their initialization sequence, and the TDX code is much more sane, e.g. invoke kvm_x86_enable_virtualization() during late_hardware_setup(), and kvm_x86_disable_virtualization() during module exit (presumably).

Fine to me, given I am not familiar with other ARCHs, assuming always enable virtualization when KVM present is fine to them. :-)

Two questions below:

arch/x86/kvm/x86.c void kvm_arch_free_vm(struct kvm *kvm) · line 12530

Quoted 5 lines
arch/x86/kvm/x86.c +5 -0
+int kvm_x86_enable_virtualization(void)
+{
+	int r;
+
+	guard(mutex)(&vendor_module_lock);

It's a little bit odd to take the vendor_module_lock mutex.

Quoted 2 lines

It is called by kvm_arch_init_vm(), so more reasonablly we should still use kvm_lock?

I think this should take an x86-specific lock, since it's guarding x86-specific data. And vendor_module_lock fits the bill perfectly. Well, except for the name, and I definitely have no objection to renaming it.

Quoted 3 lines

Also, if we invoke kvm_x86_enable_virtualization() from kvm_x86_ops->late_hardware_setup(), then IIUC we will deadlock here because kvm_x86_vendor_init() already takes the vendor_module_lock?

Ah, yeah. Oh, duh. I think the reason I didn't initially suggest late_hardware_setup() is that I was assuming/hoping TDX setup could be done after kvm_x86_vendor_exit(). E.g. in vt_init() or whatever it gets called:

	r = kvm_x86_vendor_exit(...);
	if (r)
		return r;

	if (enable_tdx) {
		r = tdx_blah_blah_blah();
		if (r)
			goto vendor_exit;
	}
Quoted 46 lines
Quoted 11 lines
arch/x86/kvm/x86.c +11 -0
+	if (kvm_usage_count++)
+		return 0;
+
+	r = kvm_enable_virtualization();
+	if (r)
+		--kvm_usage_count;
+
+	return r;
+}
+EXPORT_SYMBOL_GPL(kvm_x86_enable_virtualization);
+

[...]

Quoted 19 lines
virt/kvm/kvm_main.c +18 -0
+int kvm_enable_virtualization(void)
  {
+	int r;
+
+	r = cpuhp_setup_state(CPUHP_AP_KVM_ONLINE, "kvm/cpu:online",
+			      kvm_online_cpu, kvm_offline_cpu);
+	if (r)
+		return r;
+
+	register_syscore_ops(&kvm_syscore_ops);
+
+	/*
+	 * Manually undo virtualization enabling if the system is going down.
+	 * If userspace initiated a forced reboot, e.g. reboot -f, then it's
+	 * possible for an in-flight module load to enable virtualization
+	 * after syscore_shutdown() is called, i.e. without kvm_shutdown()
+	 * being invoked.  Note, this relies on system_state being set _before_
+	 * kvm_shutdown(), e.g. to ensure either kvm_shutdown() is invoked
+	 * or this CPU observes the impedning shutdown.  Which is why KVM uses

virt/kvm/kvm_main.c static struct syscore_ops kvm_syscore_ops = { · line 5639

Quoted 10 lines
virt/kvm/kvm_main.c +10 -0
+	 * a syscore ops hook instead of registering a dedicated reboot
+	 * notifier (the latter runs before system_state is updated).
+	 */
+	if (system_state == SYSTEM_HALT || system_state == SYSTEM_POWER_OFF ||
+	    system_state == SYSTEM_RESTART) {
+		unregister_syscore_ops(&kvm_syscore_ops);
+		cpuhp_remove_state(CPUHP_AP_KVM_ONLINE);
+		return -EBUSY;
+	}
+

Aren't we also supposed to do:

Quoted 3 lines
	on_each_cpu(__kvm_enable_virtualization, NULL, 1);

here?

No, cpuhp_setup_state() invokes the callback, kvm_online_cpu(), on each CPU. I.e. KVM has been doing things the hard way by using cpuhp_setup_state_nocalls(). That's part of the complexity I would like to get rid of.

#120 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 21 lines · Sean Christopherson
Quoted 20 lines · Kai Huang
Quoted 5 lines · Sean Christopherson

This way, architectures that aren't saddled with out-of-tree hypervisors can do the dead simple thing of enabling hardware during their initialization sequence, and the TDX code is much more sane, e.g. invoke kvm_x86_enable_virtualization() during late_hardware_setup(), and kvm_x86_disable_virtualization() during module exit (presumably).

Fine to me, given I am not familiar with other ARCHs, assuming always enable virtualization when KVM present is fine to them. :-)

Two questions below:

arch/x86/kvm/x86.c void kvm_arch_free_vm(struct kvm *kvm) · line 12530

Quoted 5 lines
arch/x86/kvm/x86.c +5 -0
+int kvm_x86_enable_virtualization(void)
+{
+	int r;
+
+	guard(mutex)(&vendor_module_lock);

It's a little bit odd to take the vendor_module_lock mutex.

Quoted 5 lines
Quoted 2 lines

It is called by kvm_arch_init_vm(), so more reasonablly we should still use kvm_lock?

I think this should take an x86-specific lock, since it's guarding x86-specific data.

OK. This makes sense.

And vendor_module_lock fits the bill perfectly. Well, except for the

Quoted 1 line

name, and I definitely have no objection to renaming it.

No opinion on renaming. Personally I wouldn't bother to rename. We can add a comment in kvm_x86_enable_virtualization() to explain. Perhaps in the future we just want to change to always enable virtualization for x86 too..

Quoted 9 lines
Quoted 4 lines

Also, if we invoke kvm_x86_enable_virtualization() from kvm_x86_ops->late_hardware_setup(), then IIUC we will deadlock here because kvm_x86_vendor_init() already takes the vendor_module_lock?

Ah, yeah. Oh, duh. I think the reason I didn't initially suggest late_hardware_setup() is that I was assuming/hoping TDX setup could be done after kvm_x86_vendor_exit(). E.g. in vt_init() or whatever it gets called:

Quoted 9 lines
	r = kvm_x86_vendor_exit(...);
	if (r)
		return r;

	if (enable_tdx) {
		r = tdx_blah_blah_blah();
		if (r)
			goto vendor_exit;
	}

I assume the reason you introduced the late_hardware_setup() is purely because you want to do:

  cpu_emergency_register_virt_callback(kvm_x86_ops.emergency_enable);

after

kvm_ops_update()?

Anyway, we can also do 'enable_tdx' outside of kvm_x86_vendor_init() as above, given it cannot be done in hardware_setup() anyway.

If we do 'enable_tdx' in late_hardware_setup(), we will need a kvm_x86_enable_virtualization_nolock(), but that's also not a problem to me.

So which way do you prefer?

Btw, with kvm_x86_virtualization_enable(), it seems the compatibility check is lost, which I assume is OK?

Btw2, currently tdx_enable() requires cpus_read_lock() must be called prior. If we do unconditional tdx_cpu_enable() in vt_hardware_enable(), then with your proposal IIUC there's no such requirement anymore, because no task will be scheduled to the new CPU before it reaches CPUHP_AP_ACTIVE. But now calling cpus_read_lock()/unlock() around tdx_enable() also acceptable to me.

[...]

Quoted 33 lines
Quoted 33 lines
Quoted 20 lines
virt/kvm/kvm_main.c +18 -0
+int kvm_enable_virtualization(void)
   {
+	int r;
+
+	r = cpuhp_setup_state(CPUHP_AP_KVM_ONLINE, "kvm/cpu:online",
+			      kvm_online_cpu, kvm_offline_cpu);
+	if (r)
+		return r;
+
+	register_syscore_ops(&kvm_syscore_ops);
+
+	/*
+	 * Manually undo virtualization enabling if the system is going down.
+	 * If userspace initiated a forced reboot, e.g. reboot -f, then it's
+	 * possible for an in-flight module load to enable virtualization
+	 * after syscore_shutdown() is called, i.e. without kvm_shutdown()
+	 * being invoked.  Note, this relies on system_state being set _before_
+	 * kvm_shutdown(), e.g. to ensure either kvm_shutdown() is invoked
+	 * or this CPU observes the impedning shutdown.  Which is why KVM uses

virt/kvm/kvm_main.c static struct syscore_ops kvm_syscore_ops = { · line 5639

Quoted 10 lines
virt/kvm/kvm_main.c +10 -0
+	 * a syscore ops hook instead of registering a dedicated reboot
+	 * notifier (the latter runs before system_state is updated).
+	 */
+	if (system_state == SYSTEM_HALT || system_state == SYSTEM_POWER_OFF ||
+	    system_state == SYSTEM_RESTART) {
+		unregister_syscore_ops(&kvm_syscore_ops);
+		cpuhp_remove_state(CPUHP_AP_KVM_ONLINE);
+		return -EBUSY;
+	}
+

Aren't we also supposed to do:

Quoted 7 lines
Quoted 3 lines
	on_each_cpu(__kvm_enable_virtualization, NULL, 1);

here?

No, cpuhp_setup_state() invokes the callback, kvm_online_cpu(), on each CPU. I.e. KVM has been doing things the hard way by using cpuhp_setup_state_nocalls(). That's part of the complexity I would like to get rid of.

Ah, right :-)

Btw, why couldn't we do the 'system_state' check at the very beginning of this function?

#121 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 16 lines · Kai Huang
Quoted 4 lines · Sean Christopherson

Ah, yeah. Oh, duh. I think the reason I didn't initially suggest late_hardware_setup() is that I was assuming/hoping TDX setup could be done after kvm_x86_vendor_exit(). E.g. in vt_init() or whatever it gets called:

Quoted 9 lines
	r = kvm_x86_vendor_exit(...);
	if (r)
		return r;

	if (enable_tdx) {
		r = tdx_blah_blah_blah();
		if (r)
			goto vendor_exit;
	}
Quoted 8 lines

I assume the reason you introduced the late_hardware_setup() is purely because you want to do:

  cpu_emergency_register_virt_callback(kvm_x86_ops.emergency_enable);

after

kvm_ops_update()?

No, kvm_ops_update() needs to come before kvm_x86_enable_virtualization(), as the static_call() to hardware_enable() needs to be patched in.

Oh, and my adjust patch is broken, the code to do the compat checks should NOT be removed; it could be removed if KVM unconditionally enabled VMX during setup, but it needs to stay in the !TDX case.

- for_each_online_cpu(cpu) {
- smp_call_function_single(cpu, kvm_x86_check_cpu_compat, &r, 1);
- if (r < 0)
- goto out_unwind_ops;
- }

Which is another reason to defer kvm_x86_enable_virtualization(), though to be honest not a particularly compelling reason on its own.

Quoted 10 lines

Anyway, we can also do 'enable_tdx' outside of kvm_x86_vendor_init() as above, given it cannot be done in hardware_setup() anyway.

If we do 'enable_tdx' in late_hardware_setup(), we will need a kvm_x86_enable_virtualization_nolock(), but that's also not a problem to me.

So which way do you prefer?

Btw, with kvm_x86_virtualization_enable(), it seems the compatibility check is lost, which I assume is OK?

Heh, and I obviously wasn't reading ahead :-)

Quoted 4 lines

Btw2, currently tdx_enable() requires cpus_read_lock() must be called prior. If we do unconditional tdx_cpu_enable() in vt_hardware_enable(), then with your proposal IIUC there's no such requirement anymore, because no task will be scheduled to the new CPU before it reaches CPUHP_AP_ACTIVE.

Correct.

Quoted 2 lines

But now calling cpus_read_lock()/unlock() around tdx_enable() also acceptable to me.

No, that will deadlock as cpuhp_setup_state() does cpus_read_lock().

Quoted 36 lines
Quoted 36 lines
Quoted 35 lines
Quoted 19 lines
virt/kvm/kvm_main.c +18 -0
+int kvm_enable_virtualization(void)
   {
+	int r;
+
+	r = cpuhp_setup_state(CPUHP_AP_KVM_ONLINE, "kvm/cpu:online",
+			      kvm_online_cpu, kvm_offline_cpu);
+	if (r)
+		return r;
+
+	register_syscore_ops(&kvm_syscore_ops);
+
+	/*
+	 * Manually undo virtualization enabling if the system is going down.
+	 * If userspace initiated a forced reboot, e.g. reboot -f, then it's
+	 * possible for an in-flight module load to enable virtualization
+	 * after syscore_shutdown() is called, i.e. without kvm_shutdown()
+	 * being invoked.  Note, this relies on system_state being set _before_
+	 * kvm_shutdown(), e.g. to ensure either kvm_shutdown() is invoked
+	 * or this CPU observes the impedning shutdown.  Which is why KVM uses

virt/kvm/kvm_main.c static struct syscore_ops kvm_syscore_ops = { · line 5639

Quoted 10 lines
virt/kvm/kvm_main.c +10 -0
+	 * a syscore ops hook instead of registering a dedicated reboot
+	 * notifier (the latter runs before system_state is updated).
+	 */
+	if (system_state == SYSTEM_HALT || system_state == SYSTEM_POWER_OFF ||
+	    system_state == SYSTEM_RESTART) {
+		unregister_syscore_ops(&kvm_syscore_ops);
+		cpuhp_remove_state(CPUHP_AP_KVM_ONLINE);
+		return -EBUSY;
+	}
+

Aren't we also supposed to do:

	on_each_cpu(__kvm_enable_virtualization, NULL, 1);

here?

Quoted 8 lines
Quoted 3 lines

No, cpuhp_setup_state() invokes the callback, kvm_online_cpu(), on each CPU. I.e. KVM has been doing things the hard way by using cpuhp_setup_state_nocalls(). That's part of the complexity I would like to get rid of.

Ah, right :-)

Btw, why couldn't we do the 'system_state' check at the very beginning of this function?

We could, but we'd still need to check after, and adding a small bit of extra complexity just to try to catch a very rare situation isn't worth it.

To prevent races, system_state needs to be check after register_syscore_ops(), because only once kvm_syscore_ops is registered is KVM guaranteed to get notified of a shutdown.

And because the kvm_syscore_ops hooks disable virtualization, they should be called after cpuhp_setup_state(). That's not strictly required, as the per-CPU hardware_enabled flag will prevent true problems if the system enter shutdown state before KVM reaches cpuhp_setup_state().

Hmm, but the same edge cases exists in the above flow. If the system enters shutdown _just_ after register_syscore_ops(), KVM would see that in system_state and do cpuhp_remove_state(), i.e. invoke kvm_offline_cpu() and thus do a double disable (which again is benign because of hardware_enabled).

Ah, but registering syscore ops before doing cpuhp_setup_state() has another race, and one that could be fatal. If the system does suspend+resume before the cpuhup hooks are registered, kvm_resume() would enable virtualization. And then if cpuhp_setup_state() failed, virtualization would be left enabled.

So cpuhp_setup_state() *must* come before register_syscore_ops(), and register_syscore_ops() *must* come before the system_state check.

#122 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 20 lines · Sean Christopherson
Quoted 19 lines · Kai Huang
Quoted 4 lines · Sean Christopherson

Ah, yeah. Oh, duh. I think the reason I didn't initially suggest late_hardware_setup() is that I was assuming/hoping TDX setup could be done after kvm_x86_vendor_exit(). E.g. in vt_init() or whatever it gets called:

Quoted 9 lines
	r = kvm_x86_vendor_exit(...);
	if (r)
		return r;

	if (enable_tdx) {
		r = tdx_blah_blah_blah();
		if (r)
			goto vendor_exit;
	}

I assume the reason you introduced the late_hardware_setup() is purely because you want to do:

Quoted 8 lines
Quoted 5 lines
   cpu_emergency_register_virt_callback(kvm_x86_ops.emergency_enable);

after

   kvm_ops_update()?

No, kvm_ops_update() needs to come before kvm_x86_enable_virtualization(), as the static_call() to hardware_enable() needs to be patched in.

Right. I was talking about that the reason you introduced the late_hardware_setup() was because we need to do kvm_x86_virtualization_enabled() and the above cpu_emergency_register_virt_callback() after kvm_ops_update().

Quoted 4 lines

Oh, and my adjust patch is broken, the code to do the compat checks should NOT be removed; it could be removed if KVM unconditionally enabled VMX during setup, but it needs to stay in the !TDX case.

Right.

Quoted 28 lines
arch/x86/kvm/x86.c +0 -5
-       for_each_online_cpu(cpu) {
-               smp_call_function_single(cpu, kvm_x86_check_cpu_compat, &r, 1);
-               if (r < 0)
-                       goto out_unwind_ops;
-       }

Which is another reason to defer kvm_x86_enable_virtualization(), though to be honest not a particularly compelling reason on its own.

Quoted 10 lines

Anyway, we can also do 'enable_tdx' outside of kvm_x86_vendor_init() as above, given it cannot be done in hardware_setup() anyway.

If we do 'enable_tdx' in late_hardware_setup(), we will need a kvm_x86_enable_virtualization_nolock(), but that's also not a problem to me.

So which way do you prefer?

Btw, with kvm_x86_virtualization_enable(), it seems the compatibility check is lost, which I assume is OK?

Heh, and I obviously wasn't reading ahead :-)

Quoted 4 lines

Btw2, currently tdx_enable() requires cpus_read_lock() must be called prior. If we do unconditional tdx_cpu_enable() in vt_hardware_enable(), then with your proposal IIUC there's no such requirement anymore, because no task will be scheduled to the new CPU before it reaches CPUHP_AP_ACTIVE.

Quoted 6 lines

Correct.

Quoted 2 lines

But now calling cpus_read_lock()/unlock() around tdx_enable() also acceptable to me.

No, that will deadlock as cpuhp_setup_state() does cpus_read_lock().

Right, but it takes cpus_read_lock()/unlock() internally. I was talking about:

	if (enable_tdx) {
		kvm_x86_virtualization_enable();

		/*
		 * Unfortunately currently tdx_enable() internally has
		 * lockdep_assert_cpus_held().
		 */
		cpus_read_lock();
		tdx_enable();
		cpus_read_unlock();
	}
Quoted 62 lines
Quoted 37 lines
Quoted 37 lines
Quoted 36 lines
Quoted 20 lines
virt/kvm/kvm_main.c +18 -0
+int kvm_enable_virtualization(void)
    {
+	int r;
+
+	r = cpuhp_setup_state(CPUHP_AP_KVM_ONLINE, "kvm/cpu:online",
+			      kvm_online_cpu, kvm_offline_cpu);
+	if (r)
+		return r;
+
+	register_syscore_ops(&kvm_syscore_ops);
+
+	/*
+	 * Manually undo virtualization enabling if the system is going down.
+	 * If userspace initiated a forced reboot, e.g. reboot -f, then it's
+	 * possible for an in-flight module load to enable virtualization
+	 * after syscore_shutdown() is called, i.e. without kvm_shutdown()
+	 * being invoked.  Note, this relies on system_state being set _before_
+	 * kvm_shutdown(), e.g. to ensure either kvm_shutdown() is invoked
+	 * or this CPU observes the impedning shutdown.  Which is why KVM uses

virt/kvm/kvm_main.c static struct syscore_ops kvm_syscore_ops = { · line 5639

Quoted 10 lines
virt/kvm/kvm_main.c +10 -0
+	 * a syscore ops hook instead of registering a dedicated reboot
+	 * notifier (the latter runs before system_state is updated).
+	 */
+	if (system_state == SYSTEM_HALT || system_state == SYSTEM_POWER_OFF ||
+	    system_state == SYSTEM_RESTART) {
+		unregister_syscore_ops(&kvm_syscore_ops);
+		cpuhp_remove_state(CPUHP_AP_KVM_ONLINE);
+		return -EBUSY;
+	}
+

Aren't we also supposed to do:

	on_each_cpu(__kvm_enable_virtualization, NULL, 1);

here?

Quoted 8 lines
Quoted 3 lines

No, cpuhp_setup_state() invokes the callback, kvm_online_cpu(), on each CPU. I.e. KVM has been doing things the hard way by using cpuhp_setup_state_nocalls(). That's part of the complexity I would like to get rid of.

Ah, right :-)

Btw, why couldn't we do the 'system_state' check at the very beginning of this function?

We could, but we'd still need to check after, and adding a small bit of extra complexity just to try to catch a very rare situation isn't worth it.

To prevent races, system_state needs to be check after register_syscore_ops(), because only once kvm_syscore_ops is registered is KVM guaranteed to get notified of a shutdown. >
And because the kvm_syscore_ops hooks disable virtualization, they should be called after cpuhp_setup_state(). That's not strictly required, as the per-CPU hardware_enabled flag will prevent true problems if the system enter shutdown state before KVM reaches cpuhp_setup_state().

Hmm, but the same edge cases exists in the above flow. If the system enters shutdown _just_ after register_syscore_ops(), KVM would see that in system_state and do cpuhp_remove_state(), i.e. invoke kvm_offline_cpu() and thus do a double disable (which again is benign because of hardware_enabled).

Quoted 7 lines

Ah, but registering syscore ops before doing cpuhp_setup_state() has another race, and one that could be fatal. If the system does suspend+resume before the cpuhup hooks are registered, kvm_resume() would enable virtualization. And then if cpuhp_setup_state() failed, virtualization would be left enabled.

So cpuhp_setup_state() *must* come before register_syscore_ops(), and register_syscore_ops() *must* come before the system_state check.

OK. I guess I have to double check here to completely understand the races. :-)

So I think we have consensus to go with the approach that shows in your second diff -- that is to always enable virtualization during module loading for all other ARCHs other than x86, for which we only always enables virtualization during module loading for TDX.

Then how about "do kvm_x86_virtualization_enable() within late_hardware_setup() in kvm_x86_vendor_init()" vs "do kvm_x86_virtualization_enable() in TDX-specific code after kvm_x86_vendor_init()"?

Which do you prefer?

#123 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 6 lines · Kai Huang
Quoted 1 line · Sean Christopherson

No, that will deadlock as cpuhp_setup_state() does cpus_read_lock().

Right, but it takes cpus_read_lock()/unlock() internally. I was talking about:

Quoted 11 lines
	if (enable_tdx) {
		kvm_x86_virtualization_enable();

		/*
		 * Unfortunately currently tdx_enable() internally has
		 * lockdep_assert_cpus_held().
		 */
		cpus_read_lock();
		tdx_enable();
		cpus_read_unlock();
	}

Ah. Just have tdx_enable() do cpus_read_lock(), I suspect/assume the current implemention was purely done in anticipation of KVM "needing" to do tdx_enable() while holding cpu_hotplug_lock.

And tdx_enable() should also do its best to verify that the caller is post-VMXON:

	if (WARN_ON_ONCE(!(__read_cr4() & X86_CR4_VMXE)))
		return -EINVAL;
Quoted 27 lines
Quoted 19 lines
Quoted 2 lines

Btw, why couldn't we do the 'system_state' check at the very beginning of this function?

We could, but we'd still need to check after, and adding a small bit of extra complexity just to try to catch a very rare situation isn't worth it.

To prevent races, system_state needs to be check after register_syscore_ops(), because only once kvm_syscore_ops is registered is KVM guaranteed to get notified of a shutdown. >
And because the kvm_syscore_ops hooks disable virtualization, they should be called after cpuhp_setup_state(). That's not strictly required, as the per-CPU hardware_enabled flag will prevent true problems if the system enter shutdown state before KVM reaches cpuhp_setup_state().

Hmm, but the same edge cases exists in the above flow. If the system enters shutdown _just_ after register_syscore_ops(), KVM would see that in system_state and do cpuhp_remove_state(), i.e. invoke kvm_offline_cpu() and thus do a double disable (which again is benign because of hardware_enabled).

Quoted 7 lines

Ah, but registering syscore ops before doing cpuhp_setup_state() has another race, and one that could be fatal. If the system does suspend+resume before the cpuhup hooks are registered, kvm_resume() would enable virtualization. And then if cpuhp_setup_state() failed, virtualization would be left enabled.

So cpuhp_setup_state() *must* come before register_syscore_ops(), and register_syscore_ops() *must* come before the system_state check.

Quoted 7 lines

OK. I guess I have to double check here to completely understand the races. :-)

So I think we have consensus to go with the approach that shows in your second diff -- that is to always enable virtualization during module loading for all other ARCHs other than x86, for which we only always enables virtualization during module loading for TDX.

Assuming the other arch maintainers are ok with that approach. If waiting until a VM is created is desirable for other architectures, then we'll need to figure out a plan b. E.g. KVM arm64 doesn't support being built as a module, so enabling hardware during initialization would mean virtualization is enabled for any kernel that is built with CONFIG_KVM=y.

Actually, duh. There's absolutely no reason to force other architectures to choose when to enable virtualization. As evidenced by the massaging to have x86 keep enabling virtualization on-demand for !TDX, the cleanups don't come from enabling virtualization during module load, they come from registering cpuup and syscore ops when virtualization is enabled.

I.e. we can keep kvm_usage_count in common code, and just do exactly what I proposed for kvm_x86_enable_virtualization().

I have patches to do this, and initial testing suggests they aren't wildly broken. I'll post them soon-ish, assuming nothing pops up in testing. They are clean enough that they can land in advance of TDX, e.g. in kvm-coco-queue even before other architectures verify I didn't break them.

Quoted 6 lines

Then how about "do kvm_x86_virtualization_enable() within late_hardware_setup() in kvm_x86_vendor_init()" vs "do kvm_x86_virtualization_enable() in TDX-specific code after kvm_x86_vendor_init()"?

Which do you prefer?

The latter, assuming it doesn't make the TDX code more complex than it needs to be. The fewer kvm_x86_ops hooks, the better.

#124 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 19 lines · Sean Christopherson
Quoted 6 lines · Kai Huang
Quoted 1 line · Sean Christopherson

No, that will deadlock as cpuhp_setup_state() does cpus_read_lock().

Right, but it takes cpus_read_lock()/unlock() internally. I was talking about:

Quoted 11 lines
	if (enable_tdx) {
		kvm_x86_virtualization_enable();

		/*
		 * Unfortunately currently tdx_enable() internally has
		 * lockdep_assert_cpus_held().
		 */
		cpus_read_lock();
		tdx_enable();
		cpus_read_unlock();
	}
Quoted 3 lines

Ah. Just have tdx_enable() do cpus_read_lock(), I suspect/assume the current implemention was purely done in anticipation of KVM "needing" to do tdx_enable() while holding cpu_hotplug_lock.

Yeah. It was implemented based on the assumption that KVM would do below sequence in hardware_setup():

	cpus_read_lock();
	on_each_cpu(vmxon_and_tdx_cpu_enable, NULL, 1);
	tdx_enable();
	on_each_cpu(vmxoff, NULL, 1);
	cpus_read_unlock();
Quoted 5 lines

And tdx_enable() should also do its best to verify that the caller is post-VMXON:

	if (WARN_ON_ONCE(!(__read_cr4() & X86_CR4_VMXE)))
		return -EINVAL;

This won't be helpful, or at least isn't sufficient.

tdx_enable() can SEAMCALLs on all online CPUs, so checking "the caller is post-VMXON" isn't enough. It needs "checking all online CPUs are in post- VMXON with tdx_cpu_enable() having been done".

I didn't add such check because it's not mandatory, i.e., the later SEAMCALL will catch such violation.

Btw, I noticed there's another problem, that is currently tdx_cpu_enable() actually requires IRQ being disabled. Again it was implemented based on it would be invoked via both on_each_cpu() and kvm_online_cpu().

It also also implemented with consideration that it could be called by multiple in-kernel TDX users in parallel via both SMP call and in normal context, so it was implemented to simply request the caller to make sure it is called with IRQ disabled so it can be IRQ safe (it uses a percpu variable to track whether TDH.SYS.LP.INIT has been done for local cpu similar to the hardware_enabled percpu variable).

Quoted 42 lines
Quoted 28 lines
Quoted 20 lines
Quoted 3 lines

Btw, why couldn't we do the 'system_state' check at the very beginning of this function?

We could, but we'd still need to check after, and adding a small bit of extra complexity just to try to catch a very rare situation isn't worth it.

To prevent races, system_state needs to be check after register_syscore_ops(), because only once kvm_syscore_ops is registered is KVM guaranteed to get notified of a shutdown. >
And because the kvm_syscore_ops hooks disable virtualization, they should be called after cpuhp_setup_state(). That's not strictly required, as the per-CPU hardware_enabled flag will prevent true problems if the system enter shutdown state before KVM reaches cpuhp_setup_state().

Hmm, but the same edge cases exists in the above flow. If the system enters shutdown _just_ after register_syscore_ops(), KVM would see that in system_state and do cpuhp_remove_state(), i.e. invoke kvm_offline_cpu() and thus do a double disable (which again is benign because of hardware_enabled).

Quoted 7 lines

Ah, but registering syscore ops before doing cpuhp_setup_state() has another race, and one that could be fatal. If the system does suspend+resume before the cpuhup hooks are registered, kvm_resume() would enable virtualization. And then if cpuhp_setup_state() failed, virtualization would be left enabled.

So cpuhp_setup_state() *must* come before register_syscore_ops(), and register_syscore_ops() *must* come before the system_state check.

Quoted 7 lines

OK. I guess I have to double check here to completely understand the races. :-)

So I think we have consensus to go with the approach that shows in your second diff -- that is to always enable virtualization during module loading for all other ARCHs other than x86, for which we only always enables virtualization during module loading for TDX.

Assuming the other arch maintainers are ok with that approach. If waiting until a VM is created is desirable for other architectures, then we'll need to figure out a plan b. E.g. KVM arm64 doesn't support being built as a module, so enabling hardware during initialization would mean virtualization is enabled for any kernel that is built with CONFIG_KVM=y.

Quoted 8 lines

Actually, duh. There's absolutely no reason to force other architectures to choose when to enable virtualization. As evidenced by the massaging to have x86 keep enabling virtualization on-demand for !TDX, the cleanups don't come from enabling virtualization during module load, they come from registering cpuup and syscore ops when virtualization is enabled.

I.e. we can keep kvm_usage_count in common code, and just do exactly what I proposed for kvm_x86_enable_virtualization().

If so, then looks this is basically changing "cpuhp_setup_state_nocalls()
+ on_each_cpu()" to "cpuhp_setup_state()", and moving it along with register_syscore_ops() to hardware_enable_all()"?

Quoted 5 lines

I have patches to do this, and initial testing suggests they aren't wildly broken. I'll post them soon-ish, assuming nothing pops up in testing. They are clean enough that they can land in advance of TDX, e.g. in kvm-coco-queue even before other architectures verify I didn't break them.

Good to know. I'll do more TDX test with them after you send them out.

Quoted 11 lines
Quoted 7 lines

Then how about "do kvm_x86_virtualization_enable() within late_hardware_setup() in kvm_x86_vendor_init()" vs "do kvm_x86_virtualization_enable() in TDX-specific code after kvm_x86_vendor_init()"?

Which do you prefer?

The latter, assuming it doesn't make the TDX code more complex than it needs to be. The fewer kvm_x86_ops hooks, the better.

Agreed.

#125 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 4 lines · Kai Huang
Quoted 3 lines · Sean Christopherson

On Fri, Apr 19, 2024, Kai Huang wrote:
And tdx_enable() should also do its best to verify that the caller is post-VMXON:

Quoted 8 lines
Quoted 2 lines
	if (WARN_ON_ONCE(!(__read_cr4() & X86_CR4_VMXE)))
		return -EINVAL;

This won't be helpful, or at least isn't sufficient.

tdx_enable() can SEAMCALLs on all online CPUs, so checking "the caller is post-VMXON" isn't enough. It needs "checking all online CPUs are in post- VMXON with tdx_cpu_enable() having been done".

I'm suggesting adding it in the responding code that does that actual SEAMCALL.

And the intent isn't to catch every possible problem. As with many sanity checks, the intent is to detect the most likely failure mode to make triaging and debugging issues a bit easier.

Quoted 2 lines

I didn't add such check because it's not mandatory, i.e., the later SEAMCALL will catch such violation.

Yeah, a sanity check definitely isn't manadatory, but I do think it would be useful and worthwhile. The code in question is relatively unique (enables VMX at module load) and a rare operation, i.e. the cost of sanity checking CR4.VMXE is meaningless. If we do end up with a bug where a CPU fails to do VMXON, this sanity check would give a decent chance of a precise report, whereas #UD on a SEAMCALL will be less clearcut.

Quoted 10 lines

Btw, I noticed there's another problem, that is currently tdx_cpu_enable() actually requires IRQ being disabled. Again it was implemented based on it would be invoked via both on_each_cpu() and kvm_online_cpu().

It also also implemented with consideration that it could be called by multiple in-kernel TDX users in parallel via both SMP call and in normal context, so it was implemented to simply request the caller to make sure it is called with IRQ disabled so it can be IRQ safe (it uses a percpu variable to track whether TDH.SYS.LP.INIT has been done for local cpu similar to the hardware_enabled percpu variable).

Is this is an actual problem, or is it just something that would need to be updated in the TDX code to handle the change in direction?

Quoted 6 lines
Quoted 6 lines

Actually, duh. There's absolutely no reason to force other architectures to choose when to enable virtualization. As evidenced by the massaging to have x86 keep enabling virtualization on-demand for !TDX, the cleanups don't come from enabling virtualization during module load, they come from registering cpuup and syscore ops when virtualization is enabled.

Quoted 6 lines
Quoted 2 lines

I.e. we can keep kvm_usage_count in common code, and just do exactly what I proposed for kvm_x86_enable_virtualization().

If so, then looks this is basically changing "cpuhp_setup_state_nocalls()
+ on_each_cpu()" to "cpuhp_setup_state()", and moving it along with register_syscore_ops() to hardware_enable_all()"?

Yep.

#126 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 8 lines · Sean Christopherson
Quoted 7 lines · Kai Huang
Quoted 5 lines · Sean Christopherson

On Fri, Apr 19, 2024, Kai Huang wrote:
And tdx_enable() should also do its best to verify that the caller is post-VMXON:

	if (WARN_ON_ONCE(!(__read_cr4() & X86_CR4_VMXE)))
		return -EINVAL;
Quoted 7 lines
Quoted 5 lines

This won't be helpful, or at least isn't sufficient.

tdx_enable() can SEAMCALLs on all online CPUs, so checking "the caller is post-VMXON" isn't enough. It needs "checking all online CPUs are in post- VMXON with tdx_cpu_enable() having been done".

I'm suggesting adding it in the responding code that does that actual SEAMCALL.

The thing is to check you will need to do additional things like making sure no scheduling would happen during "check + make SEAMCALL". Doesn't seem worth to do for me.

The intent of tdx_enable() was the caller should make sure no new CPU should come online (well this can be relaxed if we move cpuhp_setup_state() to hardware_enable_all()), and all existing online CPUs are in post-VMXON with tdx_cpu_enable() been done.

I think, if we ever need any check, the latter seems to be more reasonable.

But if we allow new CPU to become online during tdx_enable() (with your enhancement to the hardware enabling), then I don't know how to make such check at the beginning of tdx_enable(), because do

	on_each_cpu(check_seamcall_precondition, NULL, 1);

cannot catch any new CPU during tdx_enable().

Quoted 4 lines

And the intent isn't to catch every possible problem. As with many sanity checks, the intent is to detect the most likely failure mode to make triaging and debugging issues a bit easier.

The SEAMCALL will literally return a unique error code to indicate CPU isn't in post-VMXON, or tdx_cpu_enable() hasn't been done. I think the error code is already clear to pinpoint the problem (due to these pre- SEAMCALL-condition not being met).

Quoted 10 lines
Quoted 3 lines

I didn't add such check because it's not mandatory, i.e., the later SEAMCALL will catch such violation.

Yeah, a sanity check definitely isn't manadatory, but I do think it would be useful and worthwhile. The code in question is relatively unique (enables VMX at module load) and a rare operation, i.e. the cost of sanity checking CR4.VMXE is meaningless. If we do end up with a bug where a CPU fails to do VMXON, this sanity check would give a decent chance of a precise report, whereas #UD on a SEAMCALL will be less clearcut.

If VMXON fails for any CPU then cpuhp_setup_state() will fail, preventing KVM to be loaded.

And if it fails during kvm_online_cpu(), the new CPU will fail to online.

Quoted 12 lines
Quoted 11 lines

Btw, I noticed there's another problem, that is currently tdx_cpu_enable() actually requires IRQ being disabled. Again it was implemented based on it would be invoked via both on_each_cpu() and kvm_online_cpu().

It also also implemented with consideration that it could be called by multiple in-kernel TDX users in parallel via both SMP call and in normal context, so it was implemented to simply request the caller to make sure it is called with IRQ disabled so it can be IRQ safe (it uses a percpu variable to track whether TDH.SYS.LP.INIT has been done for local cpu similar to the hardware_enabled percpu variable).

Quoted 2 lines

Is this is an actual problem, or is it just something that would need to be updated in the TDX code to handle the change in direction?

For now this isn't, because KVM is the solo user, and in KVM hardware_enable_all() and kvm_online_cpu() uses kvm_lock mutex to make hardware_enable_nolock() IPI safe.

I am not sure how TDX/SEAMCALL will be used in TDX Connect.

However I needed to consider KVM as a user, so I decided to just make it must be called with IRQ disabled so I could know it is IRQ safe.

Back to the current tdx_enable() and tdx_cpu_enable(), my personal preference is, of course, to keep the existing way, that is:

During module load:

	cpus_read_lock();
	tdx_enable();
	cpus_read_unlock();

and in kvm_online_cpu():

	local_irq_save();
	tdx_cpu_enable();
	local_irq_restore();

But given KVM is the solo user now, I am also fine to change if you believe this is not acceptable.

#127 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 29 lines · Kai Huang
Quoted 8 lines · Sean Christopherson
Quoted 7 lines · Kai Huang
Quoted 5 lines · Sean Christopherson

On Fri, Apr 19, 2024, Kai Huang wrote:
And tdx_enable() should also do its best to verify that the caller is post-VMXON:

	if (WARN_ON_ONCE(!(__read_cr4() & X86_CR4_VMXE)))
		return -EINVAL;
Quoted 7 lines
Quoted 5 lines

This won't be helpful, or at least isn't sufficient.

tdx_enable() can SEAMCALLs on all online CPUs, so checking "the caller is post-VMXON" isn't enough. It needs "checking all online CPUs are in post- VMXON with tdx_cpu_enable() having been done".

I'm suggesting adding it in the responding code that does that actual SEAMCALL.

The thing is to check you will need to do additional things like making sure no scheduling would happen during "check + make SEAMCALL". Doesn't seem worth to do for me.

The intent of tdx_enable() was the caller should make sure no new CPU should come online (well this can be relaxed if we move cpuhp_setup_state() to hardware_enable_all()), and all existing online CPUs are in post-VMXON with tdx_cpu_enable() been done.

I think, if we ever need any check, the latter seems to be more reasonable.

Quoted 7 lines

But if we allow new CPU to become online during tdx_enable() (with your enhancement to the hardware enabling), then I don't know how to make such check at the beginning of tdx_enable(), because do

	on_each_cpu(check_seamcall_precondition, NULL, 1);

cannot catch any new CPU during tdx_enable().

Doh, we're talking past each other, because my initial suggestion was half-baked.

When I initially said "tdx_enable()", I didn't intend to literally mean just the CPU that calls tdx_enable(). What I was trying to say was, when doing per-CPU things when enabling TDX, sanity check that the current CPU has CR4.VMXE=1 before doing a SEAMCALL.

Ah, and now I see where the disconnect is. I was assuming tdx_enable() also did TDH_SYS_LP_INIT, but that's in a separate chunk of code that's manually invoked by KVM. More below.

Quoted 8 lines
Quoted 3 lines

And the intent isn't to catch every possible problem. As with many sanity checks, the intent is to detect the most likely failure mode to make triaging and debugging issues a bit easier.

The SEAMCALL will literally return a unique error code to indicate CPU isn't in post-VMXON, or tdx_cpu_enable() hasn't been done. I think the error code is already clear to pinpoint the problem (due to these pre- SEAMCALL-condition not being met).

No, SEAMCALL #UDs if the CPU isn't post-VMXON. I.e. the CPU doesn't make it to the TDX Module to provide a unique error code, all KVM will see is a #UD.

Quoted 32 lines
Quoted 11 lines
Quoted 10 lines

Btw, I noticed there's another problem, that is currently tdx_cpu_enable() actually requires IRQ being disabled. Again it was implemented based on it would be invoked via both on_each_cpu() and kvm_online_cpu().

It also also implemented with consideration that it could be called by multiple in-kernel TDX users in parallel via both SMP call and in normal context, so it was implemented to simply request the caller to make sure it is called with IRQ disabled so it can be IRQ safe (it uses a percpu variable to track whether TDH.SYS.LP.INIT has been done for local cpu similar to the hardware_enabled percpu variable).

Quoted 2 lines

Is this is an actual problem, or is it just something that would need to be updated in the TDX code to handle the change in direction?

For now this isn't, because KVM is the solo user, and in KVM hardware_enable_all() and kvm_online_cpu() uses kvm_lock mutex to make hardware_enable_nolock() IPI safe.

I am not sure how TDX/SEAMCALL will be used in TDX Connect.

However I needed to consider KVM as a user, so I decided to just make it must be called with IRQ disabled so I could know it is IRQ safe.

Back to the current tdx_enable() and tdx_cpu_enable(), my personal preference is, of course, to keep the existing way, that is:

During module load:

	cpus_read_lock();
	tdx_enable();
	cpus_read_unlock();
Quoted 8 lines

and in kvm_online_cpu():

	local_irq_save();
	tdx_cpu_enable();
	local_irq_restore();

But given KVM is the solo user now, I am also fine to change if you believe this is not acceptable.

Looking more closely at the code, tdx_enable() needs to be called under cpu_hotplug_lock to prevent *unplug*, i.e. to prevent the last CPU on a package from being offlined. I.e. that part's not option.

And the root of the problem/confusion is that the APIs provided by the core kernel are weird, which is really just a polite way of saying they are awful :-)

There is no reason to rely on the caller to take cpu_hotplug_lock, and definitely no reason to rely on the caller to invoke tdx_cpu_enable() separately from invoking tdx_enable(). I suspect they got that way because of KVM's unnecessarily complex code, e.g. if KVM is already doing on_each_cpu() to do VMXON, then it's easy enough to also do TDH_SYS_LP_INIT, so why do two IPIs?

But just because KVM "owns" VMXON doesn't mean the core kernel code should punt TDX to KVM too. If KVM relies on the cpuhp code to ensure all online CPUs are post-VMXON, then the TDX shapes up nicely and provides a single API to enable TDX. And then my CR4.VMXE=1 sanity check makes a _lot_ more sense.

Relative to some random version of the TDX patches, this is what I'm thinking:

---
 arch/x86/kvm/vmx/tdx.c      | 46 +++----------------
 arch/x86/virt/vmx/tdx/tdx.c | 89 ++++++++++++++++++-------------------
 2 files changed, 49 insertions(+), 86 deletions(-)
arch/x86/kvm/vmx/tdx.c +5 -41
index a1d3ae09091c..137d08da43c3 100644
@@ -3322,38 +3322,8 @@ bool tdx_is_vm_type_supported(unsigned long type)
 	return type == KVM_X86_TDX_VM;
 }
 
-struct tdx_enabled {
-	cpumask_var_t enabled;
-	atomic_t err;
-};
-
-static void __init tdx_on(void *_enable)
-{
-	struct tdx_enabled *enable = _enable;
-	int r;
-
-	r = vmx_hardware_enable();
-	if (!r) {
-		cpumask_set_cpu(smp_processor_id(), enable->enabled);
-		r = tdx_cpu_enable();
-	}
-	if (r)
-		atomic_set(&enable->err, r);
-}
-
-static void __init vmx_off(void *_enabled)
-{
-	cpumask_var_t *enabled = (cpumask_var_t *)_enabled;
-
-	if (cpumask_test_cpu(smp_processor_id(), *enabled))
-		vmx_hardware_disable();
-}
-
 int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops)
 {
-	struct tdx_enabled enable = {
-		.err = ATOMIC_INIT(0),
-	};
 	int max_pkgs;
 	int r = 0;
 	int i;
@@ -3409,17 +3379,11 @@ int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops)
 		goto out;
 	}
 
-	/* tdx_enable() in tdx_module_setup() requires cpus lock. */
-	cpus_read_lock();
-	on_each_cpu(tdx_on, &enable, true); /* TDX requires vmxon. */
-	r = atomic_read(&enable.err);
-	if (!r)
-		r = tdx_module_setup();
-	else
-		r = -EIO;
-	on_each_cpu(vmx_off, &enable.enabled, true);
-	cpus_read_unlock();
-	free_cpumask_var(enable.enabled);
+	r = tdx_enable();
+	if (r)
+		goto out;
+
+	r = tdx_module_setup();
 	if (r)
 		goto out;
 
arch/x86/virt/vmx/tdx/tdx.c +44 -45

120 lines not on this page · raw message

base-commit: fde917bc1af3e1a440ab0cb0d9364f8da25b9e17

#128 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines

And the intent isn't to catch every possible problem. As with many sanity checks, the intent is to detect the most likely failure mode to make triaging and debugging issues a bit easier.

Quoted 7 lines
Quoted 4 lines

The SEAMCALL will literally return a unique error code to indicate CPU isn't in post-VMXON, or tdx_cpu_enable() hasn't been done. I think the error code is already clear to pinpoint the problem (due to these pre- SEAMCALL-condition not being met).

No, SEAMCALL #UDs if the CPU isn't post-VMXON. I.e. the CPU doesn't make it to the TDX Module to provide a unique error code, all KVM will see is a #UD.

#UD is handled by the SEAMCALL assembly code. Please see TDX_MODULE_CALL assembly macro:

.Lseamcall_trap\@:
/*
* SEAMCALL caused #GP or #UD. By reaching here RAX contains
* the trap number. Convert the trap number to the TDX error
* code by setting TDX_SW_ERROR to the high 32-bits of RAX. *
* Note cannot OR TDX_SW_ERROR directly to RAX as OR instruction
* only accepts 32-bit immediate at most.
*/
movq $TDX_SW_ERROR, %rdi
orq %rdi, %rax

...

_ASM_EXTABLE_FAULT(.Lseamcall\@, .Lseamcall_trap\@)
.endif /* \host */

Quoted 39 lines
Quoted 39 lines
Quoted 12 lines
Quoted 11 lines

Btw, I noticed there's another problem, that is currently tdx_cpu_enable() actually requires IRQ being disabled. Again it was implemented based on it would be invoked via both on_each_cpu() and kvm_online_cpu().

It also also implemented with consideration that it could be called by multiple in-kernel TDX users in parallel via both SMP call and in normal context, so it was implemented to simply request the caller to make sure it is called with IRQ disabled so it can be IRQ safe (it uses a percpu variable to track whether TDH.SYS.LP.INIT has been done for local cpu similar to the hardware_enabled percpu variable).

Quoted 2 lines

Is this is an actual problem, or is it just something that would need to be updated in the TDX code to handle the change in direction?

For now this isn't, because KVM is the solo user, and in KVM hardware_enable_all() and kvm_online_cpu() uses kvm_lock mutex to make hardware_enable_nolock() IPI safe.

I am not sure how TDX/SEAMCALL will be used in TDX Connect.

However I needed to consider KVM as a user, so I decided to just make it must be called with IRQ disabled so I could know it is IRQ safe.

Back to the current tdx_enable() and tdx_cpu_enable(), my personal preference is, of course, to keep the existing way, that is:

During module load:

	cpus_read_lock();
	tdx_enable();
	cpus_read_unlock();

and in kvm_online_cpu():

	local_irq_save();
	tdx_cpu_enable();
	local_irq_restore();
Quoted 6 lines
Quoted 2 lines

But given KVM is the solo user now, I am also fine to change if you believe this is not acceptable.

Looking more closely at the code, tdx_enable() needs to be called under cpu_hotplug_lock to prevent *unplug*, i.e. to prevent the last CPU on a package from being offlined. I.e. that part's not option.

Yeah. We can say that. I almost forgot this :-)

Quoted 3 lines

And the root of the problem/confusion is that the APIs provided by the core kernel are weird, which is really just a polite way of saying they are awful :-)

Well, apologize for it :-)

Quoted 6 lines

There is no reason to rely on the caller to take cpu_hotplug_lock, and definitely no reason to rely on the caller to invoke tdx_cpu_enable() separately from invoking tdx_enable(). I suspect they got that way because of KVM's unnecessarily complex code, e.g. if KVM is already doing on_each_cpu() to do VMXON, then it's easy enough to also do TDH_SYS_LP_INIT, so why do two IPIs?

The main reason is we relaxed the TDH.SYS.LP.INIT to be called _after_ TDX module initialization.

Previously, the TDH.SYS.LP.INIT must be done on *ALL* CPUs that the platform has (i.e., cpu_present_mask) right after TDH.SYS.INIT and before any other SEAMCALLs. This didn't quite work with (kernel software) CPU hotplug, and it had problem dealing with things like SMT disable mitigation:

https://lore.kernel.org/lkml/[email protected]/T/#mf42fa2d68d6b98edcc2aae11dba3c2487caf3b8f

So the x86 maintainers requested to change this. The original proposal was to eliminate the entire TDH.SYS.INIT and TDH.SYS.LP.INIT:

https://lore.kernel.org/lkml/[email protected]/T/#m78c0c48078f231e92ea1b87a69bac38564d46469

But somehow it wasn't feasible, and the result was we relaxed to allow TDH.SYS.LP.INIT to be called after module initialization.

So we need a separate tdx_cpu_enable() for that.

Quoted 6 lines

But just because KVM "owns" VMXON doesn't mean the core kernel code should punt TDX to KVM too. If KVM relies on the cpuhp code to ensure all online CPUs are post-VMXON, then the TDX shapes up nicely and provides a single API to enable TDX.

We could ask tdx_enable() to invoke tdx_cpu_enable() internally, but as mentioned above we still to have the tdx_cpu_enable() as a separate API to allow CPU hotplug to call it.

Quoted 1 line

And then my CR4.VMXE=1 sanity check makes a _lot_ more sense.

As replied above the current SEAMCALL assembly returns a unique error code for that:

	#define TDX_SEAMCALL_UD		(TDX_SW_ERROR |

X86_TRAP_UD)

#129 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 108 lines · Kai Huang
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

And the intent isn't to catch every possible problem. As with many sanity checks, the intent is to detect the most likely failure mode to make triaging and debugging issues a bit easier.

Quoted 7 lines
Quoted 4 lines

The SEAMCALL will literally return a unique error code to indicate CPU isn't in post-VMXON, or tdx_cpu_enable() hasn't been done. I think the error code is already clear to pinpoint the problem (due to these pre- SEAMCALL-condition not being met).

No, SEAMCALL #UDs if the CPU isn't post-VMXON. I.e. the CPU doesn't make it to the TDX Module to provide a unique error code, all KVM will see is a #UD.

#UD is handled by the SEAMCALL assembly code. Please see TDX_MODULE_CALL assembly macro:

.Lseamcall_trap\@:
        /*
         * SEAMCALL caused #GP or #UD. By reaching here RAX contains
         * the trap number. Convert the trap number to the TDX error
         * code by setting TDX_SW_ERROR to the high 32-bits of RAX. *
         * Note cannot OR TDX_SW_ERROR directly to RAX as OR instruction
         * only accepts 32-bit immediate at most.
         */
        movq $TDX_SW_ERROR, %rdi
        orq %rdi, %rax

	...
       
	_ASM_EXTABLE_FAULT(.Lseamcall\@, .Lseamcall_trap\@)

.endif /* \host */

Quoted 39 lines
Quoted 39 lines
Quoted 12 lines
Quoted 11 lines

Btw, I noticed there's another problem, that is currently tdx_cpu_enable() actually requires IRQ being disabled. Again it was implemented based on it would be invoked via both on_each_cpu() and kvm_online_cpu().

It also also implemented with consideration that it could be called by multiple in-kernel TDX users in parallel via both SMP call and in normal context, so it was implemented to simply request the caller to make sure it is called with IRQ disabled so it can be IRQ safe (it uses a percpu variable to track whether TDH.SYS.LP.INIT has been done for local cpu similar to the hardware_enabled percpu variable).

Quoted 2 lines

Is this is an actual problem, or is it just something that would need to be updated in the TDX code to handle the change in direction?

For now this isn't, because KVM is the solo user, and in KVM hardware_enable_all() and kvm_online_cpu() uses kvm_lock mutex to make hardware_enable_nolock() IPI safe.

I am not sure how TDX/SEAMCALL will be used in TDX Connect.

However I needed to consider KVM as a user, so I decided to just make it must be called with IRQ disabled so I could know it is IRQ safe.

Back to the current tdx_enable() and tdx_cpu_enable(), my personal preference is, of course, to keep the existing way, that is:

During module load:

	cpus_read_lock();
	tdx_enable();
	cpus_read_unlock();

and in kvm_online_cpu():

	local_irq_save();
	tdx_cpu_enable();
	local_irq_restore();
Quoted 6 lines
Quoted 2 lines

But given KVM is the solo user now, I am also fine to change if you believe this is not acceptable.

Looking more closely at the code, tdx_enable() needs to be called under cpu_hotplug_lock to prevent *unplug*, i.e. to prevent the last CPU on a package from being offlined. I.e. that part's not option.

Yeah. We can say that. I almost forgot this :-)

Quoted 3 lines

And the root of the problem/confusion is that the APIs provided by the core kernel are weird, which is really just a polite way of saying they are awful :-)

Well, apologize for it :-)

Quoted 6 lines

There is no reason to rely on the caller to take cpu_hotplug_lock, and definitely no reason to rely on the caller to invoke tdx_cpu_enable() separately from invoking tdx_enable(). I suspect they got that way because of KVM's unnecessarily complex code, e.g. if KVM is already doing on_each_cpu() to do VMXON, then it's easy enough to also do TDH_SYS_LP_INIT, so why do two IPIs?

The main reason is we relaxed the TDH.SYS.LP.INIT to be called _after_ TDX module initialization.

Previously, the TDH.SYS.LP.INIT must be done on *ALL* CPUs that the platform has (i.e., cpu_present_mask) right after TDH.SYS.INIT and before any other SEAMCALLs. This didn't quite work with (kernel software) CPU hotplug, and it had problem dealing with things like SMT disable mitigation:

https://lore.kernel.org/lkml/[email protected]/T/#mf42fa2d68d6b98edcc2aae11dba3c2487caf3b8f

So the x86 maintainers requested to change this. The original proposal was to eliminate the entire TDH.SYS.INIT and TDH.SYS.LP.INIT:

Quoted 6 lines

https://lore.kernel.org/lkml/[email protected]/T/#m78c0c48078f231e92ea1b87a69bac38564d46469

But somehow it wasn't feasible, and the result was we relaxed to allow TDH.SYS.LP.INIT to be called after module initialization.

So we need a separate tdx_cpu_enable() for that.

Btw, the ideal (or probably the final) plan is to handle tdx_cpu_enable() in TDX's own CPU hotplug callback in the core-kernel and hide it from all other in-kernel TDX users.

Specifically:

1) that callback, e.g., tdx_online_cpu() will be placed _before_ any in- kernel TDX users like KVM's callback.
2) In tdx_online_cpu(), we do VMXON + tdx_cpu_enable() + VMXOFF, and return error in case of any error to prevent that cpu from going online.

That makes sure that, if TDX is supported by the platform, we basically guarantees all online CPUs are ready to issue SEAMCALL (of course, the in- kernel TDX user still needs to do VMXON for it, but that's TDX user's responsibility).

But that obviously needs to move VMXON to the core-kernel.

Currently, export tdx_cpu_enable() as a separate API and require KVM to call it explicitly is a temporary solution.

That being said, we could do tdx_cpu_enable() inside tdx_enable(), but I don't see it's a better idea.

#130 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 11 lines · Kai Huang
Quoted 10 lines · Kai Huang
Quoted 10 lines
Quoted 9 lines
Quoted 4 lines

And the intent isn't to catch every possible problem. As with many sanity checks, the intent is to detect the most likely failure mode to make triaging and debugging issues a bit easier.

The SEAMCALL will literally return a unique error code to indicate CPU isn't in post-VMXON, or tdx_cpu_enable() hasn't been done. I think the error code is already clear to pinpoint the problem (due to these pre- SEAMCALL-condition not being met).

Quoted 5 lines
Quoted 5 lines
Quoted 2 lines

No, SEAMCALL #UDs if the CPU isn't post-VMXON. I.e. the CPU doesn't make it to the TDX Module to provide a unique error code, all KVM will see is a #UD.

#UD is handled by the SEAMCALL assembly code. Please see TDX_MODULE_CALL assembly macro:

Right, but that doesn't say why the #UD occurred. The macro dresses it up in TDX_SW_ERROR so that KVM only needs a single parser, but at the end of the day KVM is still only going to see that SEAMCALL hit a #UD.

Quoted 20 lines
Quoted 20 lines
Quoted 5 lines

There is no reason to rely on the caller to take cpu_hotplug_lock, and definitely no reason to rely on the caller to invoke tdx_cpu_enable() separately from invoking tdx_enable(). I suspect they got that way because of KVM's unnecessarily complex code, e.g. if KVM is already doing on_each_cpu() to do VMXON, then it's easy enough to also do TDH_SYS_LP_INIT, so why do two IPIs?

The main reason is we relaxed the TDH.SYS.LP.INIT to be called _after_ TDX module initialization.

Previously, the TDH.SYS.LP.INIT must be done on *ALL* CPUs that the platform has (i.e., cpu_present_mask) right after TDH.SYS.INIT and before any other SEAMCALLs. This didn't quite work with (kernel software) CPU hotplug, and it had problem dealing with things like SMT disable mitigation:

https://lore.kernel.org/lkml/[email protected]/T/#mf42fa2d68d6b98edcc2aae11dba3c2487caf3b8f

So the x86 maintainers requested to change this. The original proposal was to eliminate the entire TDH.SYS.INIT and TDH.SYS.LP.INIT:

Quoted 6 lines
Quoted 6 lines

https://lore.kernel.org/lkml/[email protected]/T/#m78c0c48078f231e92ea1b87a69bac38564d46469

But somehow it wasn't feasible, and the result was we relaxed to allow TDH.SYS.LP.INIT to be called after module initialization.

So we need a separate tdx_cpu_enable() for that.

No, you don't, at least not given the TDX patches I'm looking at. Allowing TDH.SYS.LP.INIT after module initialization makes sense because otherwise the kernel would need to online all possible CPUs before initializing TDX. But that doesn't mean that the kernel needs to, or should, punt TDH.SYS.LP.INIT to KVM.

AFAICT, KVM is NOT doing TDH.SYS.LP.INIT when a CPU is onlined, only when KVM is loaded, which means that tdx_enable() can process all online CPUs just as easily as KVM.

Presumably that approach relies on something blocking onlining CPUs when TDX is active. And if that's not the case, the proposed patches are buggy.

Quoted 11 lines

Btw, the ideal (or probably the final) plan is to handle tdx_cpu_enable() in TDX's own CPU hotplug callback in the core-kernel and hide it from all other in-kernel TDX users.

Specifically:

1) that callback, e.g., tdx_online_cpu() will be placed _before_ any in- kernel TDX users like KVM's callback.
2) In tdx_online_cpu(), we do VMXON + tdx_cpu_enable() + VMXOFF, and return error in case of any error to prevent that cpu from going online.

Quoted 6 lines

That makes sure that, if TDX is supported by the platform, we basically guarantees all online CPUs are ready to issue SEAMCALL (of course, the in- kernel TDX user still needs to do VMXON for it, but that's TDX user's responsibility).

But that obviously needs to move VMXON to the core-kernel.

It doesn't strictly have to be core kernel per se, just in code that sits below KVM, e.g. in a seperate module called VAC[*] ;-)

[*] https://lore.kernel.org/all/[email protected]

Quoted 5 lines

Currently, export tdx_cpu_enable() as a separate API and require KVM to call it explicitly is a temporary solution.

That being said, we could do tdx_cpu_enable() inside tdx_enable(), but I don't see it's a better idea.

It simplifies the API surface for enabling TDX and eliminates an export.

#131 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 15 lines · Sean Christopherson
Quoted 14 lines · Kai Huang
Quoted 13 lines · Kai Huang
Quoted 12 lines
Quoted 9 lines
Quoted 4 lines

And the intent isn't to catch every possible problem. As with many sanity checks, the intent is to detect the most likely failure mode to make triaging and debugging issues a bit easier.

The SEAMCALL will literally return a unique error code to indicate CPU isn't in post-VMXON, or tdx_cpu_enable() hasn't been done. I think the error code is already clear to pinpoint the problem (due to these pre- SEAMCALL-condition not being met).

No, SEAMCALL #UDs if the CPU isn't post-VMXON. I.e. the CPU doesn't make it to the TDX Module to provide a unique error code, all KVM will see is a #UD.

Quoted 6 lines
Quoted 2 lines
Quoted 2 lines

#UD is handled by the SEAMCALL assembly code. Please see TDX_MODULE_CALL assembly macro:

Right, but that doesn't say why the #UD occurred. The macro dresses it up in TDX_SW_ERROR so that KVM only needs a single parser, but at the end of the day KVM is still only going to see that SEAMCALL hit a #UD.

Right. But is there any problem here? I thought the point was we can just use the error code to tell what went wrong.

Quoted 28 lines
Quoted 21 lines
Quoted 21 lines
Quoted 6 lines

There is no reason to rely on the caller to take cpu_hotplug_lock, and definitely no reason to rely on the caller to invoke tdx_cpu_enable() separately from invoking tdx_enable(). I suspect they got that way because of KVM's unnecessarily complex code, e.g. if KVM is already doing on_each_cpu() to do VMXON, then it's easy enough to also do TDH_SYS_LP_INIT, so why do two IPIs?

The main reason is we relaxed the TDH.SYS.LP.INIT to be called _after_ TDX module initialization.

Previously, the TDH.SYS.LP.INIT must be done on *ALL* CPUs that the platform has (i.e., cpu_present_mask) right after TDH.SYS.INIT and before any other SEAMCALLs. This didn't quite work with (kernel software) CPU hotplug, and it had problem dealing with things like SMT disable mitigation:

https://lore.kernel.org/lkml/[email protected]/T/#mf42fa2d68d6b98edcc2aae11dba3c2487caf3b8f

So the x86 maintainers requested to change this. The original proposal was to eliminate the entire TDH.SYS.INIT and TDH.SYS.LP.INIT:

Quoted 6 lines
Quoted 6 lines

https://lore.kernel.org/lkml/[email protected]/T/#m78c0c48078f231e92ea1b87a69bac38564d46469

But somehow it wasn't feasible, and the result was we relaxed to allow TDH.SYS.LP.INIT to be called after module initialization.

So we need a separate tdx_cpu_enable() for that.

Quoted 8 lines

No, you don't, at least not given the TDX patches I'm looking at. Allowing TDH.SYS.LP.INIT after module initialization makes sense because otherwise the kernel would need to online all possible CPUs before initializing TDX. But that doesn't mean that the kernel needs to, or should, punt TDH.SYS.LP.INIT to KVM.

AFAICT, KVM is NOT doing TDH.SYS.LP.INIT when a CPU is onlined, only when KVM is loaded, which means that tdx_enable() can process all online CPUs just as easily as KVM.

Hmm.. I assumed kvm_online_cpu() will do VMXON + tdx_cpu_enable().

Quoted 3 lines

Presumably that approach relies on something blocking onlining CPUs when TDX is active. And if that's not the case, the proposed patches are buggy.

The current patch ([PATCH 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module) indeed is buggy, but I don't quite follow why we need to block onlining CPU when TDX is active?

There's no hard things that prevent us to do so. KVM just need to do VMXON + tdx_cpu_enable() inside kvm_online_cpu().

Quoted 17 lines
Quoted 17 lines

Btw, the ideal (or probably the final) plan is to handle tdx_cpu_enable() in TDX's own CPU hotplug callback in the core-kernel and hide it from all other in-kernel TDX users.

Specifically:

1) that callback, e.g., tdx_online_cpu() will be placed _before_ any in- kernel TDX users like KVM's callback.
2) In tdx_online_cpu(), we do VMXON + tdx_cpu_enable() + VMXOFF, and return error in case of any error to prevent that cpu from going online.

That makes sure that, if TDX is supported by the platform, we basically guarantees all online CPUs are ready to issue SEAMCALL (of course, the in- kernel TDX user still needs to do VMXON for it, but that's TDX user's responsibility).

Quoted 6 lines
Quoted 1 line

But that obviously needs to move VMXON to the core-kernel.

It doesn't strictly have to be core kernel per se, just in code that sits below KVM, e.g. in a seperate module called VAC[*] ;-)

[*] https://lore.kernel.org/all/[email protected]

Could you elaborate why vac.ko is necessary?

Being a module natually we will need to handle module init and exit. But TDX cannot be disabled and re-enabled after initialization, so in general the vac.ko doesn't quite fit for TDX.

And I am not sure what's the fundamental difference between managing TDX module in a module vs in the core-kernel from KVM's perspective.

Quoted 8 lines
Quoted 6 lines

Currently, export tdx_cpu_enable() as a separate API and require KVM to call it explicitly is a temporary solution.

That being said, we could do tdx_cpu_enable() inside tdx_enable(), but I don't see it's a better idea.

It simplifies the API surface for enabling TDX and eliminates an export.

I was surprised to see we want to prevent onlining CPU when TDX is active.

#132 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 6 lines · Kai
Quoted 3 lines

Right, but that doesn't say why the #UD occurred.  The macro dresses it up in TDX_SW_ERROR so that KVM only needs a single parser, but at the end of the day KVM is still only going to see that SEAMCALL hit a #UD.

Right.  But is there any problem here?  I thought the point was we can just use the error code to tell what went wrong.

Oh, I guess I was replying too quickly. From the spec, #UD happens when

	IF not in VMX operation or inSMM or inSEAM or 
			((IA32_EFER.LMA & CS.L) == 0)

THEN #UD;

Are you worried about #UD was caused by other cases rather than "not in VMX operation"?

But it's quite obvious the other 3 cases are not possible, correct?

#133 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 8 lines · Kai Huang
Quoted 6 lines · Kai
Quoted 3 lines

Right, but that doesn't say why the #UD occurred.  The macro dresses it up in TDX_SW_ERROR so that KVM only needs a single parser, but at the end of the day KVM is still only going to see that SEAMCALL hit a #UD.

Right.  But is there any problem here?  I thought the point was we can just use the error code to tell what went wrong.

Quoted 8 lines

Oh, I guess I was replying too quickly. From the spec, #UD happens when

	IF not in VMX operation or inSMM or inSEAM or 
			((IA32_EFER.LMA & CS.L) == 0)

THEN #UD;

Are you worried about #UD was caused by other cases rather than "not in VMX operation"?

Yes.

Quoted 1 line

But it's quite obvious the other 3 cases are not possible, correct?

The spec I'm looking at also has:

	If IA32_VMX_PROCBASED_CTLS3[5] is 0.

And anecdotally, I know of at least one crash in our production environment where a VMX instruction hit a seemingly spurious #UD, i.e. it's not impossible for a ucode bug or hardware defect to cause problems. That's obviously _extremely_ unlikely, but that's why I emphasized that sanity checking CR4.VMXE is cheap. Practically speaking it costs nothing, so IMO it's worth adding even if the odds of it ever being helpful are one-in-and-million.

#134 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 18 lines · Sean Christopherson
Quoted 8 lines · Kai Huang
Quoted 6 lines · Kai
Quoted 3 lines

Right, but that doesn't say why the #UD occurred.  The macro dresses it up in TDX_SW_ERROR so that KVM only needs a single parser, but at the end of the day KVM is still only going to see that SEAMCALL hit a #UD.

Right.  But is there any problem here?  I thought the point was we can just use the error code to tell what went wrong.

Quoted 8 lines

Oh, I guess I was replying too quickly. From the spec, #UD happens when

	IF not in VMX operation or inSMM or inSEAM or 
			((IA32_EFER.LMA & CS.L) == 0)

THEN #UD;

Are you worried about #UD was caused by other cases rather than "not in VMX operation"?

Quoted 7 lines

Yes.

Quoted 1 line

But it's quite obvious the other 3 cases are not possible, correct?

The spec I'm looking at also has:

	If IA32_VMX_PROCBASED_CTLS3[5] is 0.

Ah, now I see this too.

It's not in the pseudo code of SEAMCALL instruction, but is at the "64-Bit Mode Exceptions" section which is after the pseudo code.

And this bit 5 is to report the capability to allow to control the "shard bit" in the 5-level EPT.

Quoted 5 lines

And anecdotally, I know of at least one crash in our production environment where a VMX instruction hit a seemingly spurious #UD, i.e. it's not impossible for a ucode bug or hardware defect to cause problems. That's obviously _extremely_ unlikely, but that's why I emphasized that sanity checking CR4.VMXE is cheap.

Yeah I agree it could happen although very unlikely.

But just to be sure:

I believe the #UD itself doesn't crash the kernel/machine, but should be the kernel unable to handle #UD in such case?

If so, I am not sure whether the CR4.VMX check can make the kernel any safer, because we can already handle the #UD for the SEAMCALL instruction.

Yeah we can clearly dump message saying "CPU isn't in VMX operation" and return failure if we have the check, but if we don't, the worst situation is we might mistakenly report "CPU isn't in VMX operation" (currently code just treats #UD as CPU not in VMX operation) when CPU doesn't IA32_VMX_PROCBASED_CTLS3[5].

And for the IA32_VMX_PROCBASED_CTLS3[5] we can easily do some pre-check in KVM code during module loading to rule out this case.

And in practice, I even believe the BIOS cannot turn on TDX if the IA32_VMX_PROCBASED_CTLS3[5] is not supported. I can check on this.

Quoted 2 lines

Practically speaking it costs nothing, so IMO it's worth adding even if the odds of it ever being helpful are one-in-and-million.

I think we will need to do below at somewhere for the common SEAMCALL function:

	unsigned long flags;
	int ret = -EINVAL;

	local_irq_save(flags);

	if (WARN_ON_ONCE(!(__read_cr4() & X86_CR4_VMXE)))
		goto out;

	ret = seamcall();
out:
	local_irq_restore(flags);
	return ret;

to make it IRQ safe.

And the odd is currently the common SEAMCALL functions, a.k.a, __seamcall() and seamcall() (the latter is a mocro actually), both return u64, so if we want to have such CR4.VMX check code in the common code, we need to invent a new error code for it.

That being said, although I agree it can make the code a little bit clearer, I am not sure whether it can make the code any safer -- even w/o it, the worst case is to incorrectly report "CPU is not in VMX operation", but shouldn't crash kernel etc.

#135 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 7 lines · Kai Huang
Quoted 5 lines · Sean Christopherson

On Tue, Apr 23, 2024, Kai Huang wrote:
And anecdotally, I know of at least one crash in our production environment where a VMX instruction hit a seemingly spurious #UD, i.e. it's not impossible for a ucode bug or hardware defect to cause problems. That's obviously _extremely_ unlikely, but that's why I emphasized that sanity checking CR4.VMXE is cheap.

Quoted 6 lines

Yeah I agree it could happen although very unlikely.

But just to be sure:

I believe the #UD itself doesn't crash the kernel/machine, but should be the kernel unable to handle #UD in such case?

Correct, the #UD is likely not (immediately) fatal.

Quoted 3 lines

If so, I am not sure whether the CR4.VMX check can make the kernel any safer, because we can already handle the #UD for the SEAMCALL instruction.

It's not about making the kernel safer, it's about helping triage/debug issues.

Quoted 11 lines

Yeah we can clearly dump message saying "CPU isn't in VMX operation" and return failure if we have the check, but if we don't, the worst situation is we might mistakenly report "CPU isn't in VMX operation" (currently code just treats #UD as CPU not in VMX operation) when CPU doesn't IA32_VMX_PROCBASED_CTLS3[5].

And for the IA32_VMX_PROCBASED_CTLS3[5] we can easily do some pre-check in KVM code during module loading to rule out this case.

And in practice, I even believe the BIOS cannot turn on TDX if the IA32_VMX_PROCBASED_CTLS3[5] is not supported. I can check on this.

Eh, I wouldn't worry about that too much. The only reason I brought up that check was to call out that we can't *know* with 100% certainty that SEAMCALL failed due to the CPU not being post-VMXON.

Quoted 19 lines
Quoted 2 lines

Practically speaking it costs nothing, so IMO it's worth adding even if the odds of it ever being helpful are one-in-and-million.

I think we will need to do below at somewhere for the common SEAMCALL function:

	unsigned long flags;
	int ret = -EINVAL;

	local_irq_save(flags);

	if (WARN_ON_ONCE(!(__read_cr4() & X86_CR4_VMXE)))
		goto out;

	ret = seamcall();
out:
	local_irq_restore(flags);
	return ret;
Quoted 6 lines

to make it IRQ safe.

And the odd is currently the common SEAMCALL functions, a.k.a, __seamcall() and seamcall() (the latter is a mocro actually), both return u64, so if we want to have such CR4.VMX check code in the common code, we need to invent a new error code for it.

Oh, I wasn't thinking that we'd check CR4.VMXE before *every* SEAMCALL, just before the TDH.SYS.LP.INIT call, i.e. before the one that is most likely to fail due to a software bug that results in the CPU not doing VMXON before enabling TDX.

Again, my intent is to add a simple, cheap, and targeted sanity check to help deal with potential failures in code that historically has been less than rock solid, and in function that has a big fat assumption that the caller has done VMXON on the CPU.

Quoted 4 lines

That being said, although I agree it can make the code a little bit clearer, I am not sure whether it can make the code any safer -- even w/o it, the worst case is to incorrectly report "CPU is not in VMX operation", but shouldn't crash kernel etc.

#136 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 31 lines · Sean Christopherson
Quoted 7 lines · Kai Huang
Quoted 5 lines · Sean Christopherson

On Tue, Apr 23, 2024, Kai Huang wrote:
And anecdotally, I know of at least one crash in our production environment where a VMX instruction hit a seemingly spurious #UD, i.e. it's not impossible for a ucode bug or hardware defect to cause problems. That's obviously _extremely_ unlikely, but that's why I emphasized that sanity checking CR4.VMXE is cheap.

Quoted 6 lines

Yeah I agree it could happen although very unlikely.

But just to be sure:

I believe the #UD itself doesn't crash the kernel/machine, but should be the kernel unable to handle #UD in such case?

Correct, the #UD is likely not (immediately) fatal.

Quoted 3 lines

If so, I am not sure whether the CR4.VMX check can make the kernel any safer, because we can already handle the #UD for the SEAMCALL instruction.

It's not about making the kernel safer, it's about helping triage/debug issues.

Quoted 9 lines

Yeah we can clearly dump message saying "CPU isn't in VMX operation" and return failure if we have the check, but if we don't, the worst situation is we might mistakenly report "CPU isn't in VMX operation" (currently code just treats #UD as CPU not in VMX operation) when CPU doesn't IA32_VMX_PROCBASED_CTLS3[5].

And for the IA32_VMX_PROCBASED_CTLS3[5] we can easily do some pre-check in KVM code during module loading to rule out this case.

Quoted 6 lines
Quoted 2 lines

And in practice, I even believe the BIOS cannot turn on TDX if the IA32_VMX_PROCBASED_CTLS3[5] is not supported. I can check on this.

Eh, I wouldn't worry about that too much. The only reason I brought up that check was to call out that we can't *know* with 100% certainty that SEAMCALL failed due to the CPU not being post-VMXON.

OK (though I think we can rule out other cases by adding more checks etc).

Quoted 32 lines
Quoted 20 lines
Quoted 3 lines

Practically speaking it costs nothing, so IMO it's worth adding even if the odds of it ever being helpful are one-in-and-million.

I think we will need to do below at somewhere for the common SEAMCALL function:

	unsigned long flags;
	int ret = -EINVAL;

	local_irq_save(flags);

	if (WARN_ON_ONCE(!(__read_cr4() & X86_CR4_VMXE)))
		goto out;

	ret = seamcall();
out:
	local_irq_restore(flags);
	return ret;
Quoted 6 lines

to make it IRQ safe.

And the odd is currently the common SEAMCALL functions, a.k.a, __seamcall() and seamcall() (the latter is a mocro actually), both return u64, so if we want to have such CR4.VMX check code in the common code, we need to invent a new error code for it.

Oh, I wasn't thinking that we'd check CR4.VMXE before *every* SEAMCALL, just before the TDH.SYS.LP.INIT call, i.e. before the one that is most likely to fail due to a software bug that results in the CPU not doing VMXON before enabling TDX.

Quoted 4 lines

Again, my intent is to add a simple, cheap, and targeted sanity check to help deal with potential failures in code that historically has been less than rock solid, and in function that has a big fat assumption that the caller has done VMXON on the CPU.

I see.

(To be fair, personally I don't recall that we ever had any bug due to "cpu not in post-VMXON before SEAMCALL", but maybe it's just me. :-).)

But if tdx_enable() doesn't call tdx_cpu_enable() internally, then we will have two functions need to handle.

For tdx_enable(), given it's still good idea to disable CPU hotplug around it, we can still do some check for all online cpus at the beginning, like:

	on_each_cpu(check_cr4_vmx(), &err, 1);

Btw, please also see my last reply to Chao why I don't like calling tdx_cpu_enable() inside tdx_enable():

https://lore.kernel.org/lkml/[email protected]/

That being said, I can try to add additional patch(es) to do CR4.VMX check if you want, but personally I found hard to have a strong justification to do so.

#137 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 16 lines · Kai Huang
Quoted 10 lines · Sean Christopherson
Quoted 4 lines

And the odd is currently the common SEAMCALL functions, a.k.a, __seamcall() and seamcall() (the latter is a mocro actually), both return u64, so if we want to have such CR4.VMX check code in the common code, we need to invent a new error code for it.

Oh, I wasn't thinking that we'd check CR4.VMXE before *every* SEAMCALL, just before the TDH.SYS.LP.INIT call, i.e. before the one that is most likely to fail due to a software bug that results in the CPU not doing VMXON before enabling TDX.

Quoted 4 lines

Again, my intent is to add a simple, cheap, and targeted sanity check to help deal with potential failures in code that historically has been less than rock solid, and in function that has a big fat assumption that the caller has done VMXON on the CPU.

Quoted 7 lines

I see.

(To be fair, personally I don't recall that we ever had any bug due to "cpu not in post-VMXON before SEAMCALL", but maybe it's just me. :-).)

But if tdx_enable() doesn't call tdx_cpu_enable() internally, then we will have two functions need to handle.

Why? I assume there will be exactly one caller of TDH.SYS.LP.INIT.

Quoted 4 lines

For tdx_enable(), given it's still good idea to disable CPU hotplug around it, we can still do some check for all online cpus at the beginning, like:

	on_each_cpu(check_cr4_vmx(), &err, 1);

If it gets to that point, just omit the check. I really think you're making much ado about nothing. My suggestion is essentially "throw in a CR4.VMXE check before TDH.SYS.LP.INIT if it's easy". If it's not easy for some reason, then don't do it.

Quoted 9 lines

Btw, please also see my last reply to Chao why I don't like calling tdx_cpu_enable() inside tdx_enable():

https://lore.kernel.org/lkml/[email protected]/

That being said, I can try to add additional patch(es) to do CR4.VMX check if you want, but personally I found hard to have a strong justification to do so.

#138 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 19 lines · Sean Christopherson
Quoted 18 lines · Kai Huang
Quoted 10 lines · Sean Christopherson
Quoted 4 lines

And the odd is currently the common SEAMCALL functions, a.k.a, __seamcall() and seamcall() (the latter is a mocro actually), both return u64, so if we want to have such CR4.VMX check code in the common code, we need to invent a new error code for it.

Oh, I wasn't thinking that we'd check CR4.VMXE before *every* SEAMCALL, just before the TDH.SYS.LP.INIT call, i.e. before the one that is most likely to fail due to a software bug that results in the CPU not doing VMXON before enabling TDX.

Quoted 4 lines

Again, my intent is to add a simple, cheap, and targeted sanity check to help deal with potential failures in code that historically has been less than rock solid, and in function that has a big fat assumption that the caller has done VMXON on the CPU.

I see.

Quoted 7 lines
Quoted 5 lines

(To be fair, personally I don't recall that we ever had any bug due to "cpu not in post-VMXON before SEAMCALL", but maybe it's just me. :-).)

But if tdx_enable() doesn't call tdx_cpu_enable() internally, then we will have two functions need to handle.

Why? I assume there will be exactly one caller of TDH.SYS.LP.INIT.

Right, it's only done in tdx_cpu_enable().

I was thinking "the one that is most likely to fail" isn't just TDH.SYS.LP.INIT in this case, but also could be any SEAMCALL that is firstly run on any online cpu inside tdx_enable().

Or perhaps you were thinking once tdx_cpu_enable() is called on one cpu, then we can safely assume that cpu must be in post-VMXON, despite we have two separate functions: tdx_cpu_enable() and tdx_enable().

Quoted 8 lines
Quoted 5 lines

For tdx_enable(), given it's still good idea to disable CPU hotplug around it, we can still do some check for all online cpus at the beginning, like:

	on_each_cpu(check_cr4_vmx(), &err, 1);

If it gets to that point, just omit the check. I really think you're making much ado about nothing.

Yeah.

Quoted 3 lines

My suggestion is essentially "throw in a CR4.VMXE check before TDH.SYS.LP.INIT if it's easy". If it's not easy for some reason, then don't do it.

I see. The disconnection between us is I am not super clear why we should treat TDH.SYS.LP.INIT as a special one that deserves a CR4.VMXE check but not other SEAMCALLs.

Anyway, I don't think adding such check or not matters a lot at this stage, and I don't want to jeopardize any more time from you on this. :-)

#139 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 8 lines · Kai Huang
Quoted 3 lines · Sean Christopherson

My suggestion is essentially "throw in a CR4.VMXE check before TDH.SYS.LP.INIT if it's easy". If it's not easy for some reason, then don't do it.

I see. The disconnection between us is I am not super clear why we should treat TDH.SYS.LP.INIT as a special one that deserves a CR4.VMXE check but not other SEAMCALLs.

Because TDH.SYS.LP.INIT is done on all CPUs via an IPI function call, is a one- time thing, and is at the intersection of core TDX and KVM module code, e.g. the the core TDX code has an explicit assumption that:

* This function assumes the caller has: 1) held read lock of CPU hotplug
* lock to prevent any new cpu from becoming online; 2) done both VMXON
* and tdx_cpu_enable() on all online cpus.

KVM can obviously screw up and attempt SEAMCALLs without being post-VMXON, but that's entirely a _KVM_ bug. And the probability of getting all the way to something like TDH_MEM_SEPT_ADD without being post-VMXON is comically low, e.g. KVM and/or the kernel would likely crash long before that point.

#140 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 10 lines · Sean Christopherson
Quoted 8 lines · Kai Huang
Quoted 3 lines · Sean Christopherson

My suggestion is essentially "throw in a CR4.VMXE check before TDH.SYS.LP.INIT if it's easy". If it's not easy for some reason, then don't do it.

I see. The disconnection between us is I am not super clear why we should treat TDH.SYS.LP.INIT as a special one that deserves a CR4.VMXE check but not other SEAMCALLs.

Quoted 7 lines

Because TDH.SYS.LP.INIT is done on all CPUs via an IPI function call, is a one- time thing, and is at the intersection of core TDX and KVM module code, e.g. the the core TDX code has an explicit assumption that:

* This function assumes the caller has: 1) held read lock of CPU hotplug
* lock to prevent any new cpu from becoming online; 2) done both VMXON
* and tdx_cpu_enable() on all online cpus.

Yeah but from this perspective, both tdx_cpu_enable() and tdx_enable() are "a one time thing" and "at the intersection of core TDX and KVM" :-)

But from the perspective that tdx_cpu_enable() must be called in IRQ disabled context, and there's no possibility that other thread/code could potentially mess up VMX enabling after the CR4.VMXE check, so it's fine to add such check.

And looking again, in fact the comment of tdx_cpu_enable() doesn't explicitly call out it requires the caller to do VMXON first (although kinda implied by the comment of tdx_enable() as you quoted above).

I can add a patch to make it more clear by calling out in the comment of tdx_cpu_enable() that it requires caller to do VMXON and adding a WARN_ON_ONCE(!CR4.VMXE) check inside. I just don't know whether it is worth to do at this stage given it's not something mandatory because it requires review time from maintainers. I can include such patch in next KVM TDX patchset if you prefer so we can see how it goes.

Quoted 5 lines

KVM can obviously screw up and attempt SEAMCALLs without being post-VMXON, but that's entirely a _KVM_ bug. And the probability of getting all the way to something like TDH_MEM_SEPT_ADD without being post-VMXON is comically low, e.g. KVM and/or the kernel would likely crash long before that point.

Yeah fully agree SEAMCALLs managed by KVM shouldn't need to do CR4.VMXE check. I was talking about those involved in tdx_enable(), i.e., TDH.SYS.xxx.

#141 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

In reply to Huang, Kai
Quoted 7 lines · Kai Huang
Quoted 2 lines · Sean Christopherson

Presumably that approach relies on something blocking onlining CPUs when TDX is active. And if that's not the case, the proposed patches are buggy.

The current patch ([PATCH 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module) indeed is buggy, but I don't quite follow why we need to block onlining CPU when TDX is active?

I was saying that based on my reading of the code, either (a) the code is buggy or (b) something blocks onlining CPUs when TDX is active. Sounds like the answer is (a).

Quoted 27 lines

There's no hard things that prevent us to do so. KVM just need to do VMXON + tdx_cpu_enable() inside kvm_online_cpu().

Quoted 17 lines
Quoted 17 lines

Btw, the ideal (or probably the final) plan is to handle tdx_cpu_enable() in TDX's own CPU hotplug callback in the core-kernel and hide it from all other in-kernel TDX users.

Specifically:

1) that callback, e.g., tdx_online_cpu() will be placed _before_ any in- kernel TDX users like KVM's callback.
2) In tdx_online_cpu(), we do VMXON + tdx_cpu_enable() + VMXOFF, and return error in case of any error to prevent that cpu from going online.

That makes sure that, if TDX is supported by the platform, we basically guarantees all online CPUs are ready to issue SEAMCALL (of course, the in- kernel TDX user still needs to do VMXON for it, but that's TDX user's responsibility).

Quoted 6 lines
Quoted 1 line

But that obviously needs to move VMXON to the core-kernel.

It doesn't strictly have to be core kernel per se, just in code that sits below KVM, e.g. in a seperate module called VAC[*] ;-)

[*] https://lore.kernel.org/all/[email protected]

Quoted 8 lines

Could you elaborate why vac.ko is necessary?

Being a module natually we will need to handle module init and exit. But TDX cannot be disabled and re-enabled after initialization, so in general the vac.ko doesn't quite fit for TDX.

And I am not sure what's the fundamental difference between managing TDX module in a module vs in the core-kernel from KVM's perspective.

VAC isn't strictly necessary. What I was saying is that it's not strictly necessary for the core kernel to handle VMXON either. I.e. it could be done in something like VAC, or it could be done in the core kernel.

The important thing is that they're handled by _one_ entity. What we have today is probably the worst setup; VMXON is handled by KVM, but TDX.SYS.LP.INIT is handled by core kernel (sort of).

#142 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 5 lines · Sean Christopherson
Quoted 4 lines · Kai Huang
Quoted 2 lines · Sean Christopherson

Presumably that approach relies on something blocking onlining CPUs when TDX is active. And if that's not the case, the proposed patches are buggy.

Quoted 7 lines
Quoted 3 lines

The current patch ([PATCH 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module) indeed is buggy, but I don't quite follow why we need to block onlining CPU when TDX is active?

I was saying that based on my reading of the code, either (a) the code is buggy or (b) something blocks onlining CPUs when TDX is active. Sounds like the answer is (a).

Yeah it's a).

Quoted 34 lines
Quoted 34 lines

There's no hard things that prevent us to do so. KVM just need to do VMXON + tdx_cpu_enable() inside kvm_online_cpu().

Quoted 17 lines
Quoted 17 lines

Btw, the ideal (or probably the final) plan is to handle tdx_cpu_enable() in TDX's own CPU hotplug callback in the core-kernel and hide it from all other in-kernel TDX users.

Specifically:

1) that callback, e.g., tdx_online_cpu() will be placed _before_ any in- kernel TDX users like KVM's callback.
2) In tdx_online_cpu(), we do VMXON + tdx_cpu_enable() + VMXOFF, and return error in case of any error to prevent that cpu from going online.

That makes sure that, if TDX is supported by the platform, we basically guarantees all online CPUs are ready to issue SEAMCALL (of course, the in- kernel TDX user still needs to do VMXON for it, but that's TDX user's responsibility).

Quoted 6 lines
Quoted 1 line

But that obviously needs to move VMXON to the core-kernel.

It doesn't strictly have to be core kernel per se, just in code that sits below KVM, e.g. in a seperate module called VAC[*] ;-)

[*] https://lore.kernel.org/all/[email protected]

Could you elaborate why vac.ko is necessary?

Being a module natually we will need to handle module init and exit. But TDX cannot be disabled and re-enabled after initialization, so in general the vac.ko doesn't quite fit for TDX.

Quoted 6 lines
Quoted 2 lines

And I am not sure what's the fundamental difference between managing TDX module in a module vs in the core-kernel from KVM's perspective.

VAC isn't strictly necessary. What I was saying is that it's not strictly necessary for the core kernel to handle VMXON either. I.e. it could be done in something like VAC, or it could be done in the core kernel.

Right, but so far I cannot see any advantage of using a VAC module, perhaps I am missing something although.

Quoted 4 lines

The important thing is that they're handled by _one_ entity. What we have today is probably the worst setup; VMXON is handled by KVM, but TDX.SYS.LP.INIT is handled by core kernel (sort of).

I cannot argue against this :-)

But from this point of view, I cannot see difference between tdx_enable() and tdx_cpu_enable(), because they both in core-kernel while depend on KVM to handle VMXON.

Or, do you prefer to we move VMXON to the core-kernel at this stage, i.e., as a prepare work for KVM TDX?

#143 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 15 lines · Kai Huang
Quoted 5 lines · Sean Christopherson
Quoted 4 lines · Kai Huang
Quoted 2 lines · Sean Christopherson

Presumably that approach relies on something blocking onlining CPUs when TDX is active. And if that's not the case, the proposed patches are buggy.

Quoted 7 lines
Quoted 3 lines

The current patch ([PATCH 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module) indeed is buggy, but I don't quite follow why we need to block onlining CPU when TDX is active?

I was saying that based on my reading of the code, either (a) the code is buggy or (b) something blocks onlining CPUs when TDX is active. Sounds like the answer is (a).

Yeah it's a).

...

Quoted 7 lines
Quoted 7 lines
Quoted 6 lines

Being a module natually we will need to handle module init and exit. But TDX cannot be disabled and re-enabled after initialization, so in general the vac.ko doesn't quite fit for TDX.

And I am not sure what's the fundamental difference between managing TDX module in a module vs in the core-kernel from KVM's perspective.

Quoted 6 lines
Quoted 3 lines

VAC isn't strictly necessary. What I was saying is that it's not strictly necessary for the core kernel to handle VMXON either. I.e. it could be done in something like VAC, or it could be done in the core kernel.

Right, but so far I cannot see any advantage of using a VAC module, perhaps I am missing something although.

Yeah, there's probably no big advantage.

Quoted 9 lines
Quoted 3 lines

The important thing is that they're handled by _one_ entity. What we have today is probably the worst setup; VMXON is handled by KVM, but TDX.SYS.LP.INIT is handled by core kernel (sort of).

I cannot argue against this :-)

But from this point of view, I cannot see difference between tdx_enable() and tdx_cpu_enable(), because they both in core-kernel while depend on KVM to handle VMXON.

My comments were made under the assumption that the code was NOT buggy, i.e. if KVM did NOT need to call tdx_cpu_enable() independent of tdx_enable().

That said, I do think it makes to have tdx_enable() call an private/inner version, e.g. __tdx_cpu_enable(), and then have KVM call a public version. Alternatively, the kernel could register yet another cpuhp hook that runs after KVM's, i.e. does TDX.SYS.LP.INIT after KVM has done VMXON (if TDX has been enabled).

Quoted 2 lines

Or, do you prefer to we move VMXON to the core-kernel at this stage, i.e., as a prepare work for KVM TDX?

No, the amount of churn/work that would create is high, and TDX is already taking on enough churn.

#144 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 12 lines
Quoted 11 lines
Quoted 5 lines

The important thing is that they're handled by _one_ entity. What we have today is probably the worst setup; VMXON is handled by KVM, but TDX.SYS.LP.INIT is handled by core kernel (sort of).

I cannot argue against this :-)

But from this point of view, I cannot see difference between tdx_enable() and tdx_cpu_enable(), because they both in core-kernel while depend on KVM to handle VMXON.

Quoted 7 lines

My comments were made under the assumption that the code was NOT buggy, i.e. if KVM did NOT need to call tdx_cpu_enable() independent of tdx_enable().

That said, I do think it makes to have tdx_enable() call an private/inner version, e.g. __tdx_cpu_enable(), and then have KVM call a public version. Alternatively, the kernel could register yet another cpuhp hook that runs after KVM's, i.e. does TDX.SYS.LP.INIT after KVM has done VMXON (if TDX has been enabled).

We will need to handle tdx_cpu_online() in "some cpuhp callback" anyway, no matter whether tdx_enable() calls __tdx_cpu_enable() internally or not, because now tdx_enable() can be done on a subset of cpus that the platform has.

For the latter (after the "Alternatively" above), by "the kernel" do you mean the core-kernel but not KVM?

E.g., you mean to register a cpuhp book _inside_ tdx_enable() after TDX is initialized successfully?

That would have problem like when KVM is not present (e.g., KVM is unloaded after it enables TDX), the cpuhp book won't work at all.

If we ever want a new TDX-specific cpuhp hook "at this stage", IMHO it's better to have it done by KVM, i.e., it goes away when KVM is unloaded.

Logically, we have two approaches in terms of how to treat tdx_cpu_enable():

1) We treat the two cases separately: calling tdx_cpu_enable() for all online cpus, and calling it when a new CPU tries to go online in some cpuhp hook.  And we only want to call tdx_cpu_enable() in cpuhp book when tdx_enable() has done successfully.

That is:

a) we always call tdx_cpu_enable() (or __tdx_cpu_enable()) inside tdx_enable() as the first step, or,

b) let the caller (KVM) to make sure of tdx_cpu_enable() has been done for all online cpus before calling tdx_enable().

Something like this:

	if (enable_tdx) {
		cpuhp_setup_state(CPUHP_AP_KVM_ONLINE, kvm_online_cpu, 
			...);

		cpus_read_lock();
		on_each_cpu(tdx_cpu_enable, ...); /* or do it inside 
						   * in tdx_enable() */
		enable_tdx = tdx_enable();
		if (enable_tdx)
			cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
				tdx_online_cpu, ...);
		cpus_read_unlock();
	}

	static int tdx_online_cpu(unsigned int cpu)
	{
		unsigned long flags;
		int ret;

		if (!enable_tdx)
			return 0;

		local_irq_save(flags);
		ret = tdx_cpu_enable();
		local_irq_restore(flags);

		return ret;
	}

2) We treat tdx_cpu_enable() as a whole by viewing it as the first step to run any TDX code (SEAMCALL) on any cpu, including the SEAMCALLs involved in tdx_enable().

That is, we *unconditionally* call tdx_cpu_enable() for all online cpus, and when a new CPU tries to go online.

This can be handled at once if we do tdx_cpu_enable() inside KVM's cpuhp hook:

	static int vt_hardware_enable(unsigned int cpu)
	{
		vmx_hardware_enable();

		local_irq_save(flags);
		ret = tdx_cpu_enable();
		local_irq_restore(flags);

		/*
		 * -ENODEV means TDX is not supported by the platform
		 * (TDX not enabled by the hardware or module is
		 * not loaded) or the kernel isn't built with TDX.
		 *
		 * Allow CPU to go online as there's no way kernel
		 * could use TDX in this case.
		 *
		 * Other error codes means TDX is available but something
		 * went wrong.  Prevent this CPU to go online so that
		 * TDX may still work on other online CPUs.
		 */
		if (ret && ret != -ENODEV)
			return ret;

		return ret;
	}

So with your change to always enable virtualization when TDX is enabled during module load, we can simply have:

	if (enable_tdx)
		cpuhp_setup_state(CPUHP_AP_KVM_ONLINE, kvm_online_cpu, 
			...);

		cpus_read_lock();
		enable_tdx = tdx_enable();
		cpus_read_unlock();
	}

So despite the cpus_read_lock() around tdx_enable() is a little bit silly, the logic is actually simpler IMHO.

(local_irq_save()/restore() around tdx_cpu_enable() is also silly but that is a common problem to both above solution and can be changed independently).

Also, as I mentioned that the final goal is to have a TDX-specific CPUHP hook in the core-kernel _BEFORE_ any in-kernel TDX user (KVM) to make sure all online CPUs are TDX-capable.

When that happens, I can just move the code in vt_hardware_enable() to tdx_online_cpu() and do additional VMXOFF inside it, with the assumption that the in-kernel TDX users should manage VMXON/VMXOFF on their own. Then all TDX users can remove the handling of tdx_cpu_enable().

#145 lore raw

Re: [PATCH v19 023/130] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 20 lines · Kai
Quoted 12 lines
Quoted 11 lines
Quoted 5 lines

The important thing is that they're handled by _one_ entity. What we have today is probably the worst setup; VMXON is handled by KVM, but TDX.SYS.LP.INIT is handled by core kernel (sort of).

I cannot argue against this :-)

But from this point of view, I cannot see difference between tdx_enable() and tdx_cpu_enable(), because they both in core-kernel while depend on KVM to handle VMXON.

Quoted 7 lines

My comments were made under the assumption that the code was NOT buggy, i.e. if KVM did NOT need to call tdx_cpu_enable() independent of tdx_enable().

That said, I do think it makes to have tdx_enable() call an private/inner version, e.g. __tdx_cpu_enable(), and then have KVM call a public version. Alternatively, the kernel could register yet another cpuhp hook that runs after KVM's, i.e. does TDX.SYS.LP.INIT after KVM has done VMXON (if TDX has been enabled).

Quoted 4 lines

We will need to handle tdx_cpu_online() in "some cpuhp callback" anyway, no matter whether tdx_enable() calls __tdx_cpu_enable() internally or not, because now tdx_enable() can be done on a subset of cpus that the platform has.

Can you confirm this is allowed again? it seems like this code indicates the opposite:

https://github.com/intel/tdx-module/blob/tdx_1.5/src/vmm_dispatcher/api_calls/tdh_sys_config.c#L768C1-L775C6

Quoted 9 lines

For the latter (after the "Alternatively" above), by "the kernel" do you mean the core-kernel but not KVM?

E.g., you mean to register a cpuhp book _inside_ tdx_enable() after TDX is initialized successfully?

That would have problem like when KVM is not present (e.g., KVM is unloaded after it enables TDX), the cpuhp book won't work at all.

Is "the cpuhp hook doesn't work if KVM is not loaded" a real problem?

The CPU about to online won't run any TDX code. So, it should be ok to skip tdx_cpu_enable().

Don't get me wrong. I don't object to registering the cpuhp hook in KVM. I just want you to make decisions based on good information.

Quoted 114 lines

If we ever want a new TDX-specific cpuhp hook "at this stage", IMHO it's better to have it done by KVM, i.e., it goes away when KVM is unloaded.

Logically, we have two approaches in terms of how to treat tdx_cpu_enable():

1) We treat the two cases separately: calling tdx_cpu_enable() for all online cpus, and calling it when a new CPU tries to go online in some cpuhp hook.  And we only want to call tdx_cpu_enable() in cpuhp book when tdx_enable() has done successfully.

That is:

a) we always call tdx_cpu_enable() (or __tdx_cpu_enable()) inside tdx_enable() as the first step, or,

b) let the caller (KVM) to make sure of tdx_cpu_enable() has been done for all online cpus before calling tdx_enable().

Something like this:

	if (enable_tdx) {
		cpuhp_setup_state(CPUHP_AP_KVM_ONLINE, kvm_online_cpu, 
			...);

		cpus_read_lock();
		on_each_cpu(tdx_cpu_enable, ...); /* or do it inside 
						   * in tdx_enable() */
		enable_tdx = tdx_enable();
		if (enable_tdx)
			cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
				tdx_online_cpu, ...);
		cpus_read_unlock();
	}

	static int tdx_online_cpu(unsigned int cpu)
	{
		unsigned long flags;
		int ret;

		if (!enable_tdx)
			return 0;

		local_irq_save(flags);
		ret = tdx_cpu_enable();
		local_irq_restore(flags);

		return ret;
	}

2) We treat tdx_cpu_enable() as a whole by viewing it as the first step to run any TDX code (SEAMCALL) on any cpu, including the SEAMCALLs involved in tdx_enable().

That is, we *unconditionally* call tdx_cpu_enable() for all online cpus, and when a new CPU tries to go online.

This can be handled at once if we do tdx_cpu_enable() inside KVM's cpuhp hook:

	static int vt_hardware_enable(unsigned int cpu)
	{
		vmx_hardware_enable();

		local_irq_save(flags);
		ret = tdx_cpu_enable();
		local_irq_restore(flags);

		/*
		 * -ENODEV means TDX is not supported by the platform
		 * (TDX not enabled by the hardware or module is
		 * not loaded) or the kernel isn't built with TDX.
		 *
		 * Allow CPU to go online as there's no way kernel
		 * could use TDX in this case.
		 *
		 * Other error codes means TDX is available but something
		 * went wrong.  Prevent this CPU to go online so that
		 * TDX may still work on other online CPUs.
		 */
		if (ret && ret != -ENODEV)
			return ret;

		return ret;
	}

So with your change to always enable virtualization when TDX is enabled during module load, we can simply have:

	if (enable_tdx)
		cpuhp_setup_state(CPUHP_AP_KVM_ONLINE, kvm_online_cpu, 
			...);

		cpus_read_lock();
		enable_tdx = tdx_enable();
		cpus_read_unlock();
	}

So despite the cpus_read_lock() around tdx_enable() is a little bit silly, the logic is actually simpler IMHO.

(local_irq_save()/restore() around tdx_cpu_enable() is also silly but that is a common problem to both above solution and can be changed independently).

Also, as I mentioned that the final goal is to have a TDX-specific CPUHP hook in the core-kernel _BEFORE_ any in-kernel TDX user (KVM) to make sure all online CPUs are TDX-capable.

When that happens, I can just move the code in vt_hardware_enable() to tdx_online_cpu() and do additional VMXOFF inside it, with the assumption that the in-kernel TDX users should manage VMXON/VMXOFF on their own. Then all TDX users can remove the handling of tdx_cpu_enable().