Skip to contentSkip to conversation

KVM TDX basic feature support

PATCH Superseded [email protected] 8 participants 221 messages Jan 12, 2023 – Mar 3, 2023 1 message not in the archive Page 1 of 2

Subsystems (7)
Documentation x86 KVM perf events +3 more subsystems

v11 of 19 Superseded Latest is v19 →

lore
Share & tools
lore
#0 lore raw

v11 cover KVM TDX basic feature support

From: Isaku Yamahata <[email protected]>

KVM TDX basic feature support

Hello. This is v11 the patch series vof KVM TDX support. This is based on v6.2-rc3 + the following patch series.

Related patch series This patch is based on:
- fd-based approach for supporing KVM v10
https://lore.kernel.org/lkml/[email protected]/
- TDX host kernel support v8
https://lore.kernel.org/lkml/[email protected]/
- KVM: Rework kvm_init() and hardware enabling v2
https://lore.kernel.org/kvm/[email protected]/

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

Major 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

Thanks,
Isaku Yamahata

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 (1):
KVM: x86: Allow to update cached values in kvm_user_return_msrs w/o
wrmsr

Chao Peng (1):
KVM: TDX: Use private memory for TDX

Isaku Yamahata (88):
KVM: x86/vmx: Refactor KVM VMX module init/exit functions
KVM: TDX: Add placeholders for TDX VM/vcpu structure
KVM: TDX: Initialize the TDX module when loading the KVM intel kernel
module
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 x86/cpu: Add helper functions to allocate/free TDX private host key id x86/virt/tdx: Add a helper function to return system wide info about
TDX module
KVM: TDX: Add place holder for TDX VM specific mem_enc_op ioctl
KVM: Support KVM_CAP_MAX_VCPUS for KVM_ENABLE_CAP
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: allocate/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: introduce config for PRIVATE KVM MMU
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
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: Require TDP MMU for TDX
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: Add flags to struct kvm_gfn_range
KVM: x86/tdp_mmu: Make handle_changed_spte() return value
KVM: x86/mmu: Make make_spte() aware of shared GPA for MTRR
KVM: x86/tdp_mmu: Support TDX private mapping for TDP MMU [MARKER] The start of TDX KVM patch series: TDX EPT violation
KVM: x86/mmu: Disallow dirty logging for x86 TDX
KVM: x86/tdp_mmu: Ignore unsupported mmu operation on private GFNs
KVM: x86/VMX: introduce vmx tlb_remote_flush and
tlb_remote_flush_with_range
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: TDX: Create initial guest memory
KVM: TDX: Finalize VM initialization
[MARKER] The start of TDX KVM patch series: TD vcpu enter/exit
KVM: TDX: Add helper assembly function to TDX vcpu
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: 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 TDX PV report fatal error hypercall
KVM: TDX: Handle TDX PV map_gpa hypercall
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 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 Documentation/virt/kvm: Document on Trust Domain Extensions(TDX)
KVM: x86: design documentation on TDX support of x86 KVM TDP MMU [MARKER] the end of (the first phase of) TDX KVM patch series

Sean Christopherson (20):
KVM: VMX: Move out vmx_x86_ops to 'main.c' to wrap VMX and TDX
KVM: x86: Introduce vm_type to differentiate default VMs from
confidential VMs
KVM: TDX: Add TDX "architectural" error codes
KVM: TDX: x86: Add ioctl to get TDX systemwide parameters
KVM: TDX: create/destroy VM structure
KVM: x86/mmu: Allow non-zero value for non-present SPTE and removed
SPTE
KVM: x86/mmu: Allow per-VM override of the TDP max page level
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: VMX: Move setting of EPT MMU masks to common VT-x code
KVM: TDX: Add accessors VMX VMCS helpers
KVM: TDX: Add load_mmu_pgd method for TDX
KVM: x86/mmu: Introduce kvm_mmu_map_tdp_page() for use by 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
KVM: TDX: Add methods to ignore accesses to CPU state

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

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         |   30 +-
 Documentation/virt/kvm/index.rst       |    3 +
 Documentation/virt/kvm/intel-tdx.rst   |  347 +++
 Documentation/virt/kvm/tdx-tdp-mmu.rst |  417 ++++
 arch/x86/events/intel/ds.c             |    1 +
 arch/x86/include/asm/kvm-x86-ops.h     |   13 +
 arch/x86/include/asm/kvm_host.h        |   75 +-
 arch/x86/include/asm/tdx.h             |   74 +-
 arch/x86/include/asm/vmx.h             |   14 +
 arch/x86/include/uapi/asm/kvm.h        |   93 +
 arch/x86/include/uapi/asm/vmx.h        |    5 +-
 arch/x86/kvm/Kconfig                   |    4 +
 arch/x86/kvm/Makefile                  |    3 +-
 arch/x86/kvm/irq.c                     |    3 +
 arch/x86/kvm/kvm_onhyperv.c            |    5 +-
 arch/x86/kvm/kvm_onhyperv.h            |    1 +
 arch/x86/kvm/lapic.c                   |   33 +-
 arch/x86/kvm/lapic.h                   |    2 +
 arch/x86/kvm/mmu.h                     |   36 +
 arch/x86/kvm/mmu/mmu.c                 |  222 +-
 arch/x86/kvm/mmu/mmu_internal.h        |  108 +-
 arch/x86/kvm/mmu/page_track.c          |    3 +
 arch/x86/kvm/mmu/paging_tmpl.h         |    3 +-
 arch/x86/kvm/mmu/spte.c                |   22 +-
 arch/x86/kvm/mmu/spte.h                |   29 +-
 arch/x86/kvm/mmu/tdp_iter.h            |   14 +-
 arch/x86/kvm/mmu/tdp_mmu.c             |  460 +++-
 arch/x86/kvm/mmu/tdp_mmu.h             |    7 +-
 arch/x86/kvm/smm.h                     |    7 +-
 arch/x86/kvm/svm/svm.c                 |    7 +
 arch/x86/kvm/svm/svm_onhyperv.h        |    1 +
 arch/x86/kvm/vmx/common.h              |  174 ++
 arch/x86/kvm/vmx/main.c                | 1118 ++++++++++
 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                 | 2753 ++++++++++++++++++++++++
 arch/x86/kvm/vmx/tdx.h                 |  257 +++
 arch/x86/kvm/vmx/tdx_arch.h            |  166 ++
 arch/x86/kvm/vmx/tdx_errno.h           |   38 +
 arch/x86/kvm/vmx/tdx_error.c           |   21 +
 arch/x86/kvm/vmx/tdx_ops.h             |  218 ++
 arch/x86/kvm/vmx/vmcs.h                |    5 +
 arch/x86/kvm/vmx/vmenter.S             |  156 ++
 arch/x86/kvm/vmx/vmx.c                 |  727 +++----
 arch/x86/kvm/vmx/vmx.h                 |   52 +-
 arch/x86/kvm/vmx/x86_ops.h             |  256 +++
 arch/x86/kvm/x86.c                     |  142 +-
 arch/x86/kvm/x86.h                     |    2 +
 arch/x86/virt/vmx/tdx/seamcall.S       |    2 +
 arch/x86/virt/vmx/tdx/tdx.c            |   56 +-
 arch/x86/virt/vmx/tdx/tdx.h            |   51 -
 include/linux/kvm_host.h               |   11 +-
 include/uapi/linux/kvm.h               |   58 +
 tools/arch/x86/include/uapi/asm/kvm.h  |   95 +
 tools/include/uapi/linux/kvm.h         |    1 +
 virt/kvm/kvm_main.c                    |   50 +-
 58 files changed, 7823 insertions(+), 758 deletions(-)
 create mode 100644 Documentation/virt/kvm/intel-tdx.rst
 create mode 100644 Documentation/virt/kvm/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: 40c18f363a0806d4f566e8a9a9bd2d7766a72cf5
prerequisite-patch-id: cb32f271215dd2080619b4a794331c8b1c518ae6
prerequisite-patch-id: 57f151e3b417beb4764b2f479e2c2cee9c24f113
prerequisite-patch-id: 3c93e412ef811eb92d0c9e7442108e57f4c0161d
prerequisite-patch-id: 9acd31e1c509affb363584255f595df52bc4aed0
prerequisite-patch-id: 63aec280487c8a9b2d47a80181b51d1f75869d47
prerequisite-patch-id: 748006fa086b15f077d6fe72ef1dd45b5d97f4d5
prerequisite-patch-id: 05377b9d771a7d1f9793d3eca9ecb18d07186c1e
prerequisite-patch-id: d89ca3c47a1995e7977058f4a2a59619bc283d9f
prerequisite-patch-id: 7817b1c896f650b8e8649028150fd6e076b35f61
prerequisite-patch-id: b9513f1b50aa3dfb13737677a0515820dcaf4a1e
prerequisite-patch-id: 8e65b22aaa8336bb25f2f368a9923e04630e626b
prerequisite-patch-id: b709d3632f19c91c124ddce7261f6eaf4853144e
prerequisite-patch-id: df4a8604d9d10328fc5996dc5133061dbd4b00c7
prerequisite-patch-id: 1c206d7035a2978c1c09521b78dd4ea81665d34a
prerequisite-patch-id: 56a319ef5c97bd66ed952513358a2d45e9f59606
prerequisite-patch-id: 343e636e705b0ee6944efbc35d457d4ff898c4b3
prerequisite-patch-id: f10c8636aaeb0fb9b7b47a1111b9c4b6ba12b1c1
prerequisite-patch-id: 003948d57f9828bbb460b680c386e30c6331c68c
prerequisite-patch-id: dc172265f050831b96447522dc8fab4231fd50d9
prerequisite-patch-id: 8d1ef7b82aba942d60171ed0877479d2a4297404
prerequisite-patch-id: e78505575e9e9f08d0166fa3eaf05c140929e319
prerequisite-patch-id: bcd4a65da6528d07f1edd628ec7f1d8df66222a0
prerequisite-patch-id: 2597d4d142448ff4796f4b9fcc5ab2e9873ae391
prerequisite-patch-id: 73023e0f63805ae226ffffe6af0798adb14c9e63
prerequisite-patch-id: daaff2e774b9e2aa139fbf89486650acc815cfa4
prerequisite-patch-id: 027b97fe2a2d10188ad1b18154a5b3a494fcdb3b
prerequisite-patch-id: 945c0546972666678bfb2b5c7737370e9f0897cb
prerequisite-patch-id: 94475f3a196844c29c9ffcfb728cecbc2baa0900
prerequisite-patch-id: 4eb88b47e0ed46c9034008bb5c9350c9e940c454
prerequisite-patch-id: 261cfb768e11e24317a4113f3cec3e65ba021696
prerequisite-patch-id: 8fbfa7cdd01d715cb8bbfe20041d0fecc691f72d
prerequisite-patch-id: c0a4c61d4e210aa0c691b26b3d4a0ab06efa617d
prerequisite-patch-id: 66322a79e5a22623c2398e9663ad23597b11347d
prerequisite-patch-id: 569bc795ecfd8c434c75bbc0202fa058c423e40e
prerequisite-patch-id: bb6bb2b65db2bc477c4b2e0e4144b9606db713a6
prerequisite-patch-id: 982f8129cad940137f6cf2c6234300b2e7d60c80
prerequisite-patch-id: f4fa117498ff3dd92db3b47c2d92e42a3d5c20f1
prerequisite-patch-id: 6f33a6b551b2c459a0083a9ec9a73fe51eb8bce1
prerequisite-patch-id: 8cc565c48b64291afb9cc817671d046cdc28a4ac
prerequisite-patch-id: 27f2d18b5ce2af402462117b522ad5a827ba3e82
prerequisite-patch-id: 40e5e3850a9402a43c31d4305f9c2e8051026328
prerequisite-patch-id: 871f2d5e5df13094f62fb8a52cb7d88ebb6146ba
prerequisite-patch-id: 176eac82fd72836b18ae8d50460f194421a00a02
prerequisite-patch-id: d38e46840d508967d1566c02ca5d1fecc39ad4ef
prerequisite-patch-id: 3160d1d6c295d51a610c71f8f9b8a0d83ee6d66c
prerequisite-patch-id: eb533cad65d1717efc48978ea6bc1c7a00cecfd2
prerequisite-patch-id: 1b4777af5019256652cf46a9719b645f35ad3545
prerequisite-patch-id: ab67f6fbf39573bf1926ed47f0f377d1907d93f9
prerequisite-patch-id: e53b862a3f19bfae6bad83a14e65399317e8f412
prerequisite-patch-id: f8c467f752f8ae76e4371637fc7f7f29ebba44c2
prerequisite-patch-id: 7746a8aef667b0d76e867bb3fe84c37e18ba9cf5
prerequisite-patch-id: ad820a6ec0b7d37bfbbd2ccba36ecb6def99f26a
prerequisite-patch-id: 24d59ae1238fb43f7337a0d08478f394441b04dc
prerequisite-patch-id: 42287c3913d32bf80cc28be41d8bf9972139dc33
prerequisite-patch-id: be3e7f787d9912862b913df033f4087d25ec5aa4
prerequisite-patch-id: a0a7ae7f18baf2ba244a5f2c38375c3352c933b8
prerequisite-patch-id: f9c3d2dd6a62adcd5c3899a5ae8aca6e511f2f4a
prerequisite-patch-id: 103d70d6a171ec88afbe7b79dacc38f45141e1f8
prerequisite-patch-id: c02ee58512800188946f1a4aecdee9c8fd9783bc
prerequisite-patch-id: 64aadbfc41067b73b942410fe4900d83e8b2fa34
prerequisite-patch-id: 97871a9f8b43b5b45c39008fe90a461eacb4eade
prerequisite-patch-id: f2be318daa505a3ec4d310438484bd27c47f08dd
prerequisite-patch-id: 4d458c35e2988d7ffa25f1b7945e84a43efffb5e
prerequisite-patch-id: 1c2f09dc37a0b4ddd186f474207d98dafa8de411
prerequisite-patch-id: c3813c0f10f83c0c0b93a84e1c31c304ff5b48e0
prerequisite-patch-id: 06060a0723d48ca605a960d819b9e3c7fe0a2bb1
prerequisite-patch-id: 3023cf5f10af5d6d129d8483ce0b28b3c2ebff57
prerequisite-patch-id: ce552534be3aa425b06f91a5e39dc7c8521cffd1
prerequisite-patch-id: ae8a3a131d7fa122e8b707cb3cbd86e74dec0cad
prerequisite-patch-id: 0c98c88257806ae7c017ce0e8db05cd77de74c82
prerequisite-patch-id: fd3fa935c3b980085ac0cb39476fb643f49a4f86
prerequisite-patch-id: 98ab27b7920907a340c10e04eadb4e6cd4a6a790
prerequisite-patch-id: 5c65c807fd9fcd77b54ca0dd0d6247910795dfbf
prerequisite-patch-id: 17e3e055e5093b47691fec7df42ecbed663b8ace
prerequisite-patch-id: dc1c8837ae740b52a864a0160d3fba842b119b09
prerequisite-patch-id: c79948a58e937f74edd94585dae796b066805569
prerequisite-patch-id: d0a71fc6ae91999a93703727e682532fe5d12c9a

Signature · 1 line
2.25.1
#1 lore raw

PATCH v11 1/113 KVM: VMX: Move out vmx_x86_ops to 'main.c' to wrap VMX and TDX

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 vxm_vcpu_free().

---
 arch/x86/kvm/Makefile      |   2 +-
 arch/x86/kvm/vmx/main.c    | 156 ++++++++++++++++
 arch/x86/kvm/vmx/vmx.c     | 361 +++++++++++--------------------------
 arch/x86/kvm/vmx/x86_ops.h | 125 +++++++++++++
 4 files changed, 384 insertions(+), 260 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 80e3fe184d17..0e894ae23cbc 100644
@@ -23,7 +23,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/hyperv.o vmx/nested.o vmx/posted_intr.o
+			   vmx/hyperv.o vmx/nested.o vmx/posted_intr.o vmx/main.o
 kvm-intel-$(CONFIG_X86_SGX_KVM)	+= vmx/sgx.o
 
 kvm-amd-y		+= svm/svm.o svm/vmenter.o svm/pmu.o svm/nested.o svm/avic.o \
arch/x86/kvm/vmx/main.c new file +156 -0

156 lines not on this page · raw message

arch/x86/kvm/vmx/vmx.c +102 -259

910 lines not on this page · raw message

arch/x86/kvm/vmx/x86_ops.h new file +125 -0

125 lines not on this page · raw message

Signature · 1 line
2.25.1
#2 lore raw

PATCH v11 2/113 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.

---
 arch/x86/kvm/vmx/main.c    | 51 +++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/vmx.c     | 54 +++++---------------------------------
 arch/x86/kvm/vmx/x86_ops.h | 13 ++++++++-
 3 files changed, 69 insertions(+), 49 deletions(-)
arch/x86/kvm/vmx/main.c +51 -0
index a39d9d68b1b3..58474511a057 100644
@@ -154,3 +154,54 @@ 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 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();
+
+	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!
+	 */
+	vt_x86_ops.vm_size = sizeof(struct kvm_vmx);
+	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 -48

104 lines not on this page · raw message

arch/x86/kvm/vmx/x86_ops.h +12 -1
index e9ec4d259ff5..051b5c4b5c2f 100644
@@ -8,11 +8,22 @@
 
 #include "x86.h"
 
-__init int vmx_hardware_setup(void);
+#if IS_ENABLED(CONFIG_HYPERV)
+__init void hv_init_evmcs(void);
+void hv_reset_evmcs(void);
+#else /* IS_ENABLED(CONFIG_HYPERV) */
+static inline void hv_init_evmcs(void) {}
+static inline void hv_reset_evmcs(void) {}
+#endif /* IS_ENABLED(CONFIG_HYPERV) */
+
+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;
 
+__init int vmx_hardware_setup(void);
 void vmx_hardware_unsetup(void);
 int vmx_check_processor_compat(void);
 int vmx_hardware_enable(void);
Signature · 1 line
2.25.1
#3 lore raw

PATCH v11 3/113 KVM: TDX: Add placeholders for TDX VM/vcpu structure

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Add placeholders TDX VM/vcpu structure that overlays with VMX VM/vcpu structures. Initialize VM structure size and vcpu size/align so that x86 KVM common code knows those size irrespective of VMX or TDX. Those structures will be populated as guest creation logic develops.

Add helper functions to check if the VM is guest TD and add conversion functions between KVM VM/VCPU and TDX VM/VCPU.

---
 arch/x86/kvm/vmx/main.c |  8 +++---
 arch/x86/kvm/vmx/tdx.h  | 54 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 3 deletions(-)
 create mode 100644 arch/x86/kvm/vmx/tdx.h
arch/x86/kvm/vmx/main.c +5 -3
index 58474511a057..18f659d1d456 100644
@@ -5,6 +5,7 @@
 #include "vmx.h"
 #include "nested.h"
 #include "pmu.h"
+#include "tdx.h"
 
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
@@ -181,9 +182,10 @@ static int __init vt_init(void)
 	 * Common KVM initialization _must_ come last, after this, /dev/kvm is
 	 * exposed to userspace!
 	 */
-	vt_x86_ops.vm_size = sizeof(struct kvm_vmx);
-	vcpu_size = sizeof(struct vcpu_vmx);
-	vcpu_align = __alignof__(struct vcpu_vmx);
+	vt_x86_ops.vm_size = max(sizeof(struct kvm_vmx), sizeof(struct kvm_tdx));
+	vcpu_size = max(sizeof(struct vcpu_vmx), sizeof(struct vcpu_tdx));
+	vcpu_align = max(__alignof__(struct vcpu_vmx),
+			 __alignof__(struct vcpu_tdx));
 	r = kvm_init(vcpu_size, vcpu_align, THIS_MODULE);
 	if (r)
 		goto err_kvm_init;
arch/x86/kvm/vmx/tdx.h new file +54 -0

54 lines not on this page · raw message

Signature · 1 line
2.25.1
#4 lore raw

PATCH v11 4/113 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), detect the TDX module availability, and initialize it. This patch implements those steps.

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 actually not. So A.) is better than B.).

Introduce a module parameter, enable_tdx, to explicitly enable TDX KVM support. It's off by default to keep same behavior for those who don't use TDX. Implement hardware_setup method to detect TDX feature of CPU. Because TDX requires all present CPUs to enable VMX (VMXON). The x86 specific kvm_arch_post_hardware_enable_setup overrides the existing weak symbol of kvm_arch_post_hardware_enable_setup which is called at the KVM module initialization.

---
 arch/x86/kvm/Makefile      |  1 +
 arch/x86/kvm/vmx/main.c    | 33 +++++++++++++++++++++++-----
 arch/x86/kvm/vmx/tdx.c     | 44 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/vmx.c     | 39 +++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 10 +++++++++
 5 files changed, 122 insertions(+), 5 deletions(-)
 create mode 100644 arch/x86/kvm/vmx/tdx.c
arch/x86/kvm/Makefile +1 -0
index 0e894ae23cbc..4b01ab842ab7 100644
@@ -25,6 +25,7 @@ kvm-$(CONFIG_KVM_SMM)	+= smm.o
 kvm-intel-y		+= vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o vmx/vmcs12.o \
 			   vmx/hyperv.o vmx/nested.o vmx/posted_intr.o vmx/main.o
 kvm-intel-$(CONFIG_X86_SGX_KVM)	+= vmx/sgx.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 svm/hyperv.o
arch/x86/kvm/vmx/main.c +28 -5
index 18f659d1d456..f5d1166d2718 100644
@@ -7,6 +7,22 @@
 #include "pmu.h"
 #include "tdx.h"
 
+static bool enable_tdx __ro_after_init = IS_ENABLED(CONFIG_INTEL_TDX_HOST);
+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;
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
@@ -149,7 +165,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,
@@ -182,10 +198,17 @@ static int __init vt_init(void)
 	 * Common KVM initialization _must_ come last, after this, /dev/kvm is
 	 * exposed to userspace!
 	 */
-	vt_x86_ops.vm_size = max(sizeof(struct kvm_vmx), sizeof(struct kvm_tdx));
-	vcpu_size = max(sizeof(struct vcpu_vmx), sizeof(struct vcpu_tdx));
-	vcpu_align = max(__alignof__(struct vcpu_vmx),
-			 __alignof__(struct vcpu_tdx));
+	vt_x86_ops.vm_size = sizeof(struct kvm_vmx);
+	vcpu_size = sizeof(struct vcpu_vmx);
+	vcpu_align = __alignof__(struct vcpu_vmx);
+	if (enable_tdx) {
+		vt_x86_ops.vm_size = max_t(unsigned int, vt_x86_ops.vm_size,
+					   sizeof(struct kvm_tdx));
+		vcpu_size = max_t(unsigned int, vcpu_size,
+				  sizeof(struct vcpu_tdx));
+		vcpu_align = max_t(unsigned int, vcpu_align,
+				   __alignof__(struct vcpu_tdx));
+	}
 	r = kvm_init(vcpu_size, vcpu_align, THIS_MODULE);
 	if (r)
 		goto err_kvm_init;
arch/x86/kvm/vmx/tdx.c new file +44 -0

44 lines not on this page · raw message

arch/x86/kvm/vmx/vmx.c +39 -0
index 5de1792c9902..5dc7687dcf16 100644
@@ -8147,6 +8147,45 @@ static unsigned int vmx_handle_intel_pt_intr(void)
 	return 1;
 }
 
+static __init void vmxon(void *arg)
+{
+	int cpu = raw_smp_processor_id();
+	u64 phys_addr = __pa(per_cpu(vmxarea, cpu));
+	atomic_t *failed = arg;
+	int r;
+
+	if (cr4_read_shadow() & X86_CR4_VMXE) {
+		r = -EBUSY;
+		goto out;
+	}
+
+	r = kvm_cpu_vmxon(phys_addr);
+out:
+	if (r)
+		atomic_inc(failed);
+}
+
+__init int vmxon_all(void)
+{
+	atomic_t failed = ATOMIC_INIT(0);
+
+	on_each_cpu(vmxon, &failed, 1);
+
+	if (atomic_read(&failed))
+		return -EBUSY;
+	return 0;
+}
+
+static __init void vmxoff(void *junk)
+{
+	cpu_vmxoff();
+}
+
+__init void vmxoff_all(void)
+{
+	on_each_cpu(vmxoff, NULL, 1);
+}
+
 static __init void vmx_setup_user_return_msrs(void)
 {
 
arch/x86/kvm/vmx/x86_ops.h +10 -0
index 051b5c4b5c2f..fbc57fcbdd21 100644
@@ -20,6 +20,10 @@ bool kvm_is_vmx_supported(void);
 int __init vmx_init(void);
 void vmx_exit(void);
 
+__init int vmxon_all(void);
+__init void vmxoff_all(void);
+__init int vmx_hardware_setup(void);
+
 extern struct kvm_x86_ops vt_x86_ops __initdata;
 extern struct kvm_x86_init_ops vt_init_ops __initdata;
 
@@ -133,4 +137,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 0; }
+#endif
+
 #endif /* __KVM_X86_VMX_X86_OPS_H */
Signature · 1 line
2.25.1
#5 lore raw

Re: [PATCH v11 004/113] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 56 lines · On Thu, 12 Jan 2023 08:31:12 -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), detect the TDX module availability, and initialize it. This patch implements those steps.

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 actually not. So A.) is better than B.).

Introduce a module parameter, enable_tdx, to explicitly enable TDX KVM support. It's off by default to keep same behavior for those who don't use TDX. Implement hardware_setup method to detect TDX feature of CPU. Because TDX requires all present CPUs to enable VMX (VMXON). The x86 specific kvm_arch_post_hardware_enable_setup overrides the existing weak symbol of kvm_arch_post_hardware_enable_setup which is called at the KVM module initialization.

---
 arch/x86/kvm/Makefile      |  1 +
 arch/x86/kvm/vmx/main.c    | 33 +++++++++++++++++++++++-----
 arch/x86/kvm/vmx/tdx.c     | 44 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/vmx.c     | 39 +++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 10 +++++++++
 5 files changed, 122 insertions(+), 5 deletions(-)
 create mode 100644 arch/x86/kvm/vmx/tdx.c
arch/x86/kvm/Makefile +0 -0
index 0e894ae23cbc..4b01ab842ab7 100644
@@ -25,6 +25,7 @@ kvm-$(CONFIG_KVM_SMM)	+= smm.o
 kvm-intel-y		+= vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o

vmx/vmcs12.o \ vmx/hyperv.o vmx/nested.o vmx/posted_intr.o vmx/main.o
kvm-intel-$(CONFIG_X86_SGX_KVM) += vmx/sgx.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 svm/hyperv.o

arch/x86/kvm/vmx/main.c +1 -0
index 18f659d1d456..f5d1166d2718 100644
@@ -7,6 +7,22 @@
 #include "pmu.h"
 #include "tdx.h"
 
+static bool enable_tdx __ro_after_init =
 IS_ENABLED(CONFIG_INTEL_TDX_HOST); +module_param_named(tdx, enable_tdx,
Quoted 1 line

bool, 0444); +

The comments says "TDX is off by default". It seems default on/off is controlled by the kernel configuration here.

Quoted 64 lines
arch/x86/kvm/vmx/main.c +14 -1
+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;
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
@@ -149,7 +165,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,
@@ -182,10 +198,17 @@ static int __init vt_init(void)
 	 * Common KVM initialization _must_ come last, after this,

/dev/kvm is

arch/x86/kvm/vmx/main.c +0 -1
 	 * exposed to userspace!
 	 */
-	vt_x86_ops.vm_size = max(sizeof(struct kvm_vmx), sizeof(struct

kvm_tdx));

arch/x86/kvm/vmx/main.c +0 -1
-	vcpu_size = max(sizeof(struct vcpu_vmx), sizeof(struct

vcpu_tdx));

arch/x86/kvm/vmx/main.c +5 -2
-	vcpu_align = max(__alignof__(struct vcpu_vmx),
-			 __alignof__(struct vcpu_tdx));
+	vt_x86_ops.vm_size = sizeof(struct kvm_vmx);
+	vcpu_size = sizeof(struct vcpu_vmx);
+	vcpu_align = __alignof__(struct vcpu_vmx);
+	if (enable_tdx) {
+		vt_x86_ops.vm_size = max_t(unsigned int,

vt_x86_ops.vm_size,

arch/x86/kvm/vmx/main.c +6 -0
+					   sizeof(struct kvm_tdx));
+		vcpu_size = max_t(unsigned int, vcpu_size,
+				  sizeof(struct vcpu_tdx));
+		vcpu_align = max_t(unsigned int, vcpu_align,
+				   __alignof__(struct vcpu_tdx));
+	}
 	r = kvm_init(vcpu_size, vcpu_align, THIS_MODULE);
 	if (r)
 		goto err_kvm_init;
arch/x86/kvm/vmx/tdx.c new file +7 -0
new file mode 100644
index 000000000000..d7a276118940
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/cpu.h>
+
+#include <asm/tdx.h>
+
+#include "capabilities.h"
+#include "x86_ops.h"

arch/x86/kvm/vmx/tdx.c line 17

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c new file +10 -0
+#include "tdx.h"
+#include "x86.h"
+
+#undef pr_fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+static int __init tdx_module_setup(void)
+{
+	int ret;
+

Better mention the tdx_enable() is implemented in another patch? But I guess we need a wrapper here so that the compilation would succeed.

Quoted 103 lines
arch/x86/kvm/vmx/tdx.c new file +27 -0
+	ret = tdx_enable();
+	if (ret) {
+		pr_info("Failed to initialize TDX module.\n");
+		return ret;
+	}
+
+	pr_info("TDX is supported.\n");
+	return 0;
+}
+
+int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops)
+{
+	int r;
+
+	if (!enable_ept) {
+		pr_warn("Cannot enable TDX with EPT disabled\n");
+		return -EINVAL;
+	}
+
+	/* TDX requires VMX. */
+	r = vmxon_all();
+	if (!r)
+		r = tdx_module_setup();
+	vmxoff_all();
+
+	return r;
+}
arch/x86/kvm/vmx/vmx.c +39 -0
index 5de1792c9902..5dc7687dcf16 100644
@@ -8147,6 +8147,45 @@ static unsigned int vmx_handle_intel_pt_intr(void)
 	return 1;
 }
 
+static __init void vmxon(void *arg)
+{
+	int cpu = raw_smp_processor_id();
+	u64 phys_addr = __pa(per_cpu(vmxarea, cpu));
+	atomic_t *failed = arg;
+	int r;
+
+	if (cr4_read_shadow() & X86_CR4_VMXE) {
+		r = -EBUSY;
+		goto out;
+	}
+
+	r = kvm_cpu_vmxon(phys_addr);
+out:
+	if (r)
+		atomic_inc(failed);
+}
+
+__init int vmxon_all(void)
+{
+	atomic_t failed = ATOMIC_INIT(0);
+
+	on_each_cpu(vmxon, &failed, 1);
+
+	if (atomic_read(&failed))
+		return -EBUSY;
+	return 0;
+}
+
+static __init void vmxoff(void *junk)
+{
+	cpu_vmxoff();
+}
+
+__init void vmxoff_all(void)
+{
+	on_each_cpu(vmxoff, NULL, 1);
+}
+
 static __init void vmx_setup_user_return_msrs(void)
 {
 
arch/x86/kvm/vmx/x86_ops.h +8 -0
index 051b5c4b5c2f..fbc57fcbdd21 100644
@@ -20,6 +20,10 @@ bool kvm_is_vmx_supported(void);
 int __init vmx_init(void);
 void vmx_exit(void);
 
+__init int vmxon_all(void);
+__init void vmxoff_all(void);
+__init int vmx_hardware_setup(void);
+
 extern struct kvm_x86_ops vt_x86_ops __initdata;
 extern struct kvm_x86_init_ops vt_init_ops __initdata;
 
@@ -133,4 +137,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 0; } +#endif
+
#endif /* __KVM_X86_VMX_X86_OPS_H */

#6 lore raw

Re: [PATCH v11 004/113] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 57 lines · Zhi Wang
Quoted 54 lines · On Thu, 12 Jan 2023 08:31:12 -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), detect the TDX module availability, and initialize it. This patch implements those steps.

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 actually not. So A.) is better than B.).

Introduce a module parameter, enable_tdx, to explicitly enable TDX KVM support. It's off by default to keep same behavior for those who don't use TDX. Implement hardware_setup method to detect TDX feature of CPU. Because TDX requires all present CPUs to enable VMX (VMXON). The x86 specific kvm_arch_post_hardware_enable_setup overrides the existing weak symbol of kvm_arch_post_hardware_enable_setup which is called at the KVM module initialization.

---
 arch/x86/kvm/Makefile      |  1 +
 arch/x86/kvm/vmx/main.c    | 33 +++++++++++++++++++++++-----
 arch/x86/kvm/vmx/tdx.c     | 44 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/vmx.c     | 39 +++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 10 +++++++++
 5 files changed, 122 insertions(+), 5 deletions(-)
 create mode 100644 arch/x86/kvm/vmx/tdx.c
arch/x86/kvm/Makefile +0 -0
index 0e894ae23cbc..4b01ab842ab7 100644
@@ -25,6 +25,7 @@ kvm-$(CONFIG_KVM_SMM)	+= smm.o
 kvm-intel-y		+= vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o

vmx/vmcs12.o \ vmx/hyperv.o vmx/nested.o vmx/posted_intr.o vmx/main.o
kvm-intel-$(CONFIG_X86_SGX_KVM) += vmx/sgx.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 svm/hyperv.o

arch/x86/kvm/vmx/main.c +0 -0
index 18f659d1d456..f5d1166d2718 100644
@@ -7,6 +7,22 @@
 #include "pmu.h"
 #include "tdx.h"
 
Quoted 6 lines
Quoted 3 lines
arch/x86/kvm/vmx/main.c +1 -0
+static bool enable_tdx __ro_after_init =
 IS_ENABLED(CONFIG_INTEL_TDX_HOST); +module_param_named(tdx, enable_tdx,

bool, 0444); +

The comments says "TDX is off by default". It seems default on/off is controlled by the kernel configuration here.

I'll update the comments.

Quoted 69 lines
Quoted 69 lines
arch/x86/kvm/vmx/main.c +14 -1
+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;
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
@@ -149,7 +165,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,
@@ -182,10 +198,17 @@ static int __init vt_init(void)
 	 * Common KVM initialization _must_ come last, after this,

/dev/kvm is

arch/x86/kvm/vmx/main.c +0 -1
 	 * exposed to userspace!
 	 */
-	vt_x86_ops.vm_size = max(sizeof(struct kvm_vmx), sizeof(struct

kvm_tdx));

arch/x86/kvm/vmx/main.c +0 -1
-	vcpu_size = max(sizeof(struct vcpu_vmx), sizeof(struct

vcpu_tdx));

arch/x86/kvm/vmx/main.c +5 -2
-	vcpu_align = max(__alignof__(struct vcpu_vmx),
-			 __alignof__(struct vcpu_tdx));
+	vt_x86_ops.vm_size = sizeof(struct kvm_vmx);
+	vcpu_size = sizeof(struct vcpu_vmx);
+	vcpu_align = __alignof__(struct vcpu_vmx);
+	if (enable_tdx) {
+		vt_x86_ops.vm_size = max_t(unsigned int,

vt_x86_ops.vm_size,

arch/x86/kvm/vmx/main.c +6 -0
+					   sizeof(struct kvm_tdx));
+		vcpu_size = max_t(unsigned int, vcpu_size,
+				  sizeof(struct vcpu_tdx));
+		vcpu_align = max_t(unsigned int, vcpu_align,
+				   __alignof__(struct vcpu_tdx));
+	}
 	r = kvm_init(vcpu_size, vcpu_align, THIS_MODULE);
 	if (r)
 		goto err_kvm_init;
arch/x86/kvm/vmx/tdx.c new file +12 -0
new file mode 100644
index 000000000000..d7a276118940
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/cpu.h>
+
+#include <asm/tdx.h>
+
+#include "capabilities.h"
+#include "x86_ops.h"
+#include "tdx.h"
+#include "x86.h"
+
+#undef pr_fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c line 17

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c new file +5 -0
+
+static int __init tdx_module_setup(void)
+{
+	int ret;
+

Better mention the tdx_enable() is implemented in another patch? But I guess we need a wrapper here so that the compilation would succeed.

The coverletter mentions it. I'll make the commit message of this patch mention it anyway.

Quoted 104 lines
Quoted 103 lines
arch/x86/kvm/vmx/tdx.c new file +27 -0
+	ret = tdx_enable();
+	if (ret) {
+		pr_info("Failed to initialize TDX module.\n");
+		return ret;
+	}
+
+	pr_info("TDX is supported.\n");
+	return 0;
+}
+
+int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops)
+{
+	int r;
+
+	if (!enable_ept) {
+		pr_warn("Cannot enable TDX with EPT disabled\n");
+		return -EINVAL;
+	}
+
+	/* TDX requires VMX. */
+	r = vmxon_all();
+	if (!r)
+		r = tdx_module_setup();
+	vmxoff_all();
+
+	return r;
+}
arch/x86/kvm/vmx/vmx.c +39 -0
index 5de1792c9902..5dc7687dcf16 100644
@@ -8147,6 +8147,45 @@ static unsigned int vmx_handle_intel_pt_intr(void)
 	return 1;
 }
 
+static __init void vmxon(void *arg)
+{
+	int cpu = raw_smp_processor_id();
+	u64 phys_addr = __pa(per_cpu(vmxarea, cpu));
+	atomic_t *failed = arg;
+	int r;
+
+	if (cr4_read_shadow() & X86_CR4_VMXE) {
+		r = -EBUSY;
+		goto out;
+	}
+
+	r = kvm_cpu_vmxon(phys_addr);
+out:
+	if (r)
+		atomic_inc(failed);
+}
+
+__init int vmxon_all(void)
+{
+	atomic_t failed = ATOMIC_INIT(0);
+
+	on_each_cpu(vmxon, &failed, 1);
+
+	if (atomic_read(&failed))
+		return -EBUSY;
+	return 0;
+}
+
+static __init void vmxoff(void *junk)
+{
+	cpu_vmxoff();
+}
+
+__init void vmxoff_all(void)
+{
+	on_each_cpu(vmxoff, NULL, 1);
+}
+
 static __init void vmx_setup_user_return_msrs(void)
 {
 
arch/x86/kvm/vmx/x86_ops.h +8 -0
index 051b5c4b5c2f..fbc57fcbdd21 100644
@@ -20,6 +20,10 @@ bool kvm_is_vmx_supported(void);
 int __init vmx_init(void);
 void vmx_exit(void);
 
+__init int vmxon_all(void);
+__init void vmxoff_all(void);
+__init int vmx_hardware_setup(void);
+
 extern struct kvm_x86_ops vt_x86_ops __initdata;
 extern struct kvm_x86_init_ops vt_init_ops __initdata;
 
@@ -133,4 +137,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 0; } +#endif
+
#endif /* __KVM_X86_VMX_X86_OPS_H */

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

Re: [PATCH v11 004/113] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

Quoted 5 lines · Isaku Yamahata
Quoted 2 lines

Better mention the tdx_enable() is implemented in another patch? But I guess we need a wrapper here so that the compilation would succeed.

The coverletter mentions it. I'll make the commit message of this patch mention it anyway.

When this series is merged, the TDX host series must have already been merged.

You obviously don't need to say something like "tdx_enable() is implemented in another patch/series" in _changelog_.

#8 lore raw

Re: [PATCH v11 004/113] KVM: TDX: Initialize the TDX module when loading the KVM intel kernel module

In reply to [email protected]
Quoted 5 lines · On Thu, 2023-01-12 at 08:31 -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), detect the TDX module availability, and initialize it. This patch implements those steps.

"detect the TDX module" is not needed anymore.

Btw, I guess you should get rid of "This patch ...". Please see below quoted txt from https://docs.kernel.org/process/submitting-patches.html :

"
Describe your changes in imperative mood, e.g. “make xyzzy do frotz” instead of “[This patch] makes xyzzy do frotz” or “[I] changed xyzzy to do frotz”, as if you are giving orders to the codebase to change its behaviour.
"

Quoted 9 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. 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 actually not. So A.) is better than B.).

Quoted 4 lines

Introduce a module parameter, enable_tdx, to explicitly enable TDX KVM support. It's off by default to keep same behavior for those who don't use TDX. Implement hardware_setup method to detect TDX feature of CPU. Because TDX requires all present CPUs to enable VMX (VMXON). The x86

"Because TDX ... , the x86 specific ...".

Quoted 32 lines

specific kvm_arch_post_hardware_enable_setup overrides the existing weak symbol of kvm_arch_post_hardware_enable_setup which is called at the KVM module initialization.

---
 arch/x86/kvm/Makefile      |  1 +
 arch/x86/kvm/vmx/main.c    | 33 +++++++++++++++++++++++-----
 arch/x86/kvm/vmx/tdx.c     | 44 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/vmx.c     | 39 +++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 10 +++++++++
 5 files changed, 122 insertions(+), 5 deletions(-)
 create mode 100644 arch/x86/kvm/vmx/tdx.c
arch/x86/kvm/Makefile +1 -0
index 0e894ae23cbc..4b01ab842ab7 100644
@@ -25,6 +25,7 @@ kvm-$(CONFIG_KVM_SMM)	+= smm.o
 kvm-intel-y		+= vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o vmx/vmcs12.o \
 			   vmx/hyperv.o vmx/nested.o vmx/posted_intr.o vmx/main.o
 kvm-intel-$(CONFIG_X86_SGX_KVM)	+= vmx/sgx.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 svm/hyperv.o
arch/x86/kvm/vmx/main.c +0 -0
index 18f659d1d456..f5d1166d2718 100644

arch/x86/kvm/vmx/main.c line 10

Quoted 4 lines
arch/x86/kvm/vmx/main.c +1 -0
@@ -7,6 +7,22 @@
 #include "pmu.h"
 #include "tdx.h"
 
+static bool enable_tdx __ro_after_init = IS_ENABLED(CONFIG_INTEL_TDX_HOST);

The changelog said it's off by default, so not consistent here.

Quoted 36 lines
arch/x86/kvm/vmx/main.c +17 -5
+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;
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
@@ -149,7 +165,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,
@@ -182,10 +198,17 @@ static int __init vt_init(void)
 	 * Common KVM initialization _must_ come last, after this, /dev/kvm is
 	 * exposed to userspace!
 	 */
-	vt_x86_ops.vm_size = max(sizeof(struct kvm_vmx), sizeof(struct kvm_tdx));
-	vcpu_size = max(sizeof(struct vcpu_vmx), sizeof(struct vcpu_tdx));
-	vcpu_align = max(__alignof__(struct vcpu_vmx),
-			 __alignof__(struct vcpu_tdx));
+	vt_x86_ops.vm_size = sizeof(struct kvm_vmx);

arch/x86/kvm/vmx/main.c static int __init vt_init(void) · line 211

Quoted 10 lines
arch/x86/kvm/vmx/main.c +10 -0
+	vcpu_size = sizeof(struct vcpu_vmx);
+	vcpu_align = __alignof__(struct vcpu_vmx);
+	if (enable_tdx) {
+		vt_x86_ops.vm_size = max_t(unsigned int, vt_x86_ops.vm_size,
+					   sizeof(struct kvm_tdx));
+		vcpu_size = max_t(unsigned int, vcpu_size,
+				  sizeof(struct vcpu_tdx));
+		vcpu_align = max_t(unsigned int, vcpu_align,
+				   __alignof__(struct vcpu_tdx));
+	}

You have below code in the previous patch:

@@ -181,9 +182,10 @@ static int __init vt_init(void)
* Common KVM initialization _must_ come last, after this, /dev/kvm is
* exposed to userspace!
*/
- vt_x86_ops.vm_size = sizeof(struct kvm_vmx);
- vcpu_size = sizeof(struct vcpu_vmx);
- vcpu_align = __alignof__(struct vcpu_vmx);
+ vt_x86_ops.vm_size = max(sizeof(struct kvm_vmx), sizeof(struct
kvm_tdx));
+ vcpu_size = max(sizeof(struct vcpu_vmx), sizeof(struct vcpu_tdx));
+ vcpu_align = max(__alignof__(struct vcpu_vmx),
+ __alignof__(struct vcpu_tdx));

The chunk here in this patch is not related how to initialize the TDX module, but belong to "how KVM handles TDX at VM level and vcpu level", which is introduced in the previous patch, which isn't mandatory to be introduced before this patch.

IMHO a better way is to move the previous patch after this one, and just put this chunk to that patch. And if you do so, this chunk of change can just appear once but not twice in two patches.

[snip]

arch/x86/kvm/vmx/main.c static int __init vt_init(void)

Quoted 5 lines
arch/x86/kvm/vmx/main.c +4 -0
+#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 0; }

Why do you return 0, which is a success IIUC?

#9 lore raw

PATCH v11 5/113 KVM: x86: Introduce vm_type to differentiate default VMs from confidential VMs

In reply to [email protected]

From: Sean Christopherson <[email protected]>

Unlike default VMs, confidential VMs (Intel TDX and AMD SEV-ES) don't allow some operations (e.g., memory read/write, register state access, etc).

Introduce vm_type to track the type of the VM to x86 KVM. Other arch KVMs already use vm_type, KVM_INIT_VM accepts vm_type, and x86 KVM callback vm_init accepts vm_type. So follow them. Further, a different policy can be made based on vm_type. Define KVM_X86_DEFAULT_VM for default VM as default and define KVM_X86_TDX_VM for Intel TDX VM. The wrapper function will be defined as "bool is_td(kvm) { return vm_type == VM_TYPE_TDX; }"

Add a capability KVM_CAP_VM_TYPES to effectively allow device model, e.g. qemu, to query what VM types are supported by KVM. This (introduce a new capability and add vm_type) is chosen to align with other arch KVMs that have VM types already. Other arch KVMs uses different name to query supported vm types and there is no common name for it, so new name was chosen.

---
 Documentation/virt/kvm/api.rst        | 21 +++++++++++++++++++++
 arch/x86/include/asm/kvm-x86-ops.h    |  1 +
 arch/x86/include/asm/kvm_host.h       |  2 ++
 arch/x86/include/uapi/asm/kvm.h       |  3 +++
 arch/x86/kvm/svm/svm.c                |  6 ++++++
 arch/x86/kvm/vmx/main.c               |  1 +
 arch/x86/kvm/vmx/tdx.h                |  6 +-----
 arch/x86/kvm/vmx/vmx.c                |  5 +++++
 arch/x86/kvm/vmx/x86_ops.h            |  1 +
 arch/x86/kvm/x86.c                    |  9 ++++++++-
 include/uapi/linux/kvm.h              |  1 +
 tools/arch/x86/include/uapi/asm/kvm.h |  3 +++
 tools/include/uapi/linux/kvm.h        |  1 +
 13 files changed, 54 insertions(+), 6 deletions(-)
Documentation/virt/kvm/api.rst +21 -0
index 98459999273c..d2baa05f7c04 100644
@@ -147,10 +147,31 @@ described as 'basic' will be available.
 The new VM has no virtual cpus and no memory.
 You probably want to use 0 as machine type.
 
+X86:
+^^^^
+
+Supported vm type can be queried from KVM_CAP_VM_TYPES, which returns the
+bitmap of supported vm types. The 1-setting of bit @n means vm type with
+value @n is supported.
+
+S390:
+^^^^^
+
 In order to create user controlled virtual machines on S390, check
 KVM_CAP_S390_UCONTROL and use the flag KVM_VM_S390_UCONTROL as
 privileged user (CAP_SYS_ADMIN).
 
+MIPS:
+^^^^^
+
+To use hardware assisted virtualization on MIPS (VZ ASE) rather than
+the default trap & emulate implementation (which changes the virtual
+memory layout to fit in user mode), check KVM_CAP_MIPS_VZ and use the
+flag KVM_VM_MIPS_VZ.
+
+ARM64:
+^^^^^^
+
 On arm64, the physical address size for a VM (IPA Size limit) is limited
 to 40bits by default. The limit can be configured if the host supports the
 extension KVM_CAP_ARM_VM_IPA_SIZE. When supported, use
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index dba2909e5ae2..59181b12ad70 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 +2 -0
index 60dc8f1631de..c6ccfce7dc9e 100644
@@ -1212,6 +1212,7 @@ enum kvm_apicv_inhibit {
 };
 
 struct kvm_arch {
+	unsigned long vm_type;
 	unsigned long n_used_mmu_pages;
 	unsigned long n_requested_mmu_pages;
 	unsigned long n_max_mmu_pages;
@@ -1536,6 +1537,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 +3 -0
index e48deab8901d..a4cca6bc6b06 100644
@@ -529,4 +529,7 @@ struct kvm_pmu_event_filter {
 #define KVM_VCPU_TSC_CTRL 0 /* control group for the timestamp counter (TSC) */
 #define   KVM_VCPU_TSC_OFFSET 0 /* attribute for the TSC offset */
 
+#define KVM_X86_DEFAULT_VM	0
+#define KVM_X86_TDX_VM		1
+
 #endif /* _ASM_X86_KVM_H */
arch/x86/kvm/svm/svm.c +6 -0
index 799b24801d31..55f2e0a9b0f6 100644
@@ -4682,6 +4682,11 @@ static void svm_vm_destroy(struct kvm *kvm)
 	sev_vm_destroy(kvm);
 }
 
+static bool svm_is_vm_type_supported(unsigned long type)
+{
+	return type == KVM_X86_DEFAULT_VM;
+}
+
 static int svm_vm_init(struct kvm *kvm)
 {
 	if (!pause_filter_count || !pause_filter_thresh)
@@ -4710,6 +4715,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/main.c +1 -0
index f5d1166d2718..3b24e32077d6 100644
@@ -34,6 +34,7 @@ struct kvm_x86_ops vt_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/vmx/tdx.h +1 -5
index 060bf48ec3d6..473013265bd8 100644
@@ -15,11 +15,7 @@ struct vcpu_tdx {
 
 static inline bool is_td(struct kvm *kvm)
 {
-	/*
-	 * TDX VM type isn't defined yet.
-	 * return kvm->arch.vm_type == KVM_X86_TDX_VM;
-	 */
-	return false;
+	return kvm->arch.vm_type == KVM_X86_TDX_VM;
 }
 
 static inline bool is_td_vcpu(struct kvm_vcpu *vcpu)
arch/x86/kvm/vmx/vmx.c +5 -0
index 5dc7687dcf16..f1dea386d6c2 100644
@@ -7501,6 +7501,11 @@ int vmx_vcpu_create(struct kvm_vcpu *vcpu)
 	return err;
 }
 
+bool vmx_is_vm_type_supported(unsigned long type)
+{
+	return type == KVM_X86_DEFAULT_VM;
+}
+
 #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"
 
arch/x86/kvm/vmx/x86_ops.h +1 -0
index fbc57fcbdd21..6980126bc32a 100644
@@ -32,6 +32,7 @@ void vmx_hardware_unsetup(void);
 int vmx_check_processor_compat(void);
 int vmx_hardware_enable(void);
 void vmx_hardware_disable(void);
+bool vmx_is_vm_type_supported(unsigned long type);
 int vmx_vm_init(struct kvm *kvm);
 void vmx_vm_destroy(struct kvm *kvm);
 int vmx_vcpu_precreate(struct kvm *kvm);
arch/x86/kvm/x86.c +8 -1
index 07e8ab791e37..68bff699096a 100644
@@ -4535,6 +4535,11 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 	case KVM_CAP_X86_NOTIFY_VMEXIT:
 		r = kvm_caps.has_notify_vmexit;
 		break;
+	case KVM_CAP_VM_TYPES:
+		r = BIT(KVM_X86_DEFAULT_VM);
+		if (static_call(kvm_x86_is_vm_type_supported)(KVM_X86_TDX_VM))
+			r |= BIT(KVM_X86_TDX_VM);
+		break;
 	default:
 		break;
 	}
@@ -12126,9 +12131,11 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
 	int ret;
 	unsigned long flags;
 
-	if (type)
+	if (!static_call(kvm_x86_is_vm_type_supported)(type))
 		return -EINVAL;
 
+	kvm->arch.vm_type = type;
+
 	ret = kvm_page_track_init(kvm);
 	if (ret)
 		goto out;
include/uapi/linux/kvm.h +1 -0
index 679d293ece0f..2a47fd0e51fd 100644
@@ -1212,6 +1212,7 @@ struct kvm_ppc_resize_hpt {
 #define KVM_CAP_S390_PROTECTED_ASYNC_DISABLE 224
 #define KVM_CAP_DIRTY_LOG_RING_WITH_BITMAP 225
 #define KVM_CAP_MEMORY_ATTRIBUTES 226
+#define KVM_CAP_VM_TYPES 227
 
 #ifdef KVM_CAP_IRQ_ROUTING
 
tools/arch/x86/include/uapi/asm/kvm.h +3 -0
index 649e50a8f9dd..b67d2d59eb6c 100644
@@ -524,4 +524,7 @@ struct kvm_pmu_event_filter {
 #define KVM_VCPU_TSC_CTRL 0 /* control group for the timestamp counter (TSC) */
 #define   KVM_VCPU_TSC_OFFSET 0 /* attribute for the TSC offset */
 
+#define KVM_X86_DEFAULT_VM	0
+#define KVM_X86_TDX_VM		1
+
 #endif /* _ASM_X86_KVM_H */
tools/include/uapi/linux/kvm.h +1 -0
index 20522d4ba1e0..792a4889d1f4 100644
@@ -1175,6 +1175,7 @@ struct kvm_ppc_resize_hpt {
 #define KVM_CAP_DIRTY_LOG_RING_ACQ_REL 223
 #define KVM_CAP_S390_PROTECTED_ASYNC_DISABLE 224
 #define KVM_CAP_DIRTY_LOG_RING_WITH_BITMAP 225
+#define KVM_CAP_VM_TYPES 227
 
 #ifdef KVM_CAP_IRQ_ROUTING
 
Signature · 1 line
2.25.1
#10 lore raw

Re: [PATCH v11 005/113] KVM: x86: Introduce vm_type to differentiate default VMs from confidential VMs

Quoted 12 lines · On 1/13/2023 12:31 AM, [email protected] wrote:

From: Sean Christopherson <[email protected]>

Unlike default VMs, confidential VMs (Intel TDX and AMD SEV-ES) don't allow some operations (e.g., memory read/write, register state access, etc).

Introduce vm_type to track the type of the VM to x86 KVM. Other arch KVMs already use vm_type, KVM_INIT_VM accepts vm_type, and x86 KVM callback vm_init accepts vm_type. So follow them. Further, a different policy can be made based on vm_type. Define KVM_X86_DEFAULT_VM for default VM as default and define KVM_X86_TDX_VM for Intel TDX VM. The wrapper function will be defined as "bool is_td(kvm) { return vm_type == VM_TYPE_TDX; }"

Quoted 4 lines

Add a capability KVM_CAP_VM_TYPES to effectively allow device model, e.g. qemu, to query what VM types are supported by KVM. This (introduce a new capability and add vm_type) is chosen to align with other arch KVMs that have VM types already. Other arch KVMs uses

uses -> use

Quoted 1 line

different name

name -> names

Quoted 251 lines

to query
supported vm types and there is no common name for it, so new name was chosen.

---
  Documentation/virt/kvm/api.rst        | 21 +++++++++++++++++++++
  arch/x86/include/asm/kvm-x86-ops.h    |  1 +
  arch/x86/include/asm/kvm_host.h       |  2 ++
  arch/x86/include/uapi/asm/kvm.h       |  3 +++
  arch/x86/kvm/svm/svm.c                |  6 ++++++
  arch/x86/kvm/vmx/main.c               |  1 +
  arch/x86/kvm/vmx/tdx.h                |  6 +-----
  arch/x86/kvm/vmx/vmx.c                |  5 +++++
  arch/x86/kvm/vmx/x86_ops.h            |  1 +
  arch/x86/kvm/x86.c                    |  9 ++++++++-
  include/uapi/linux/kvm.h              |  1 +
  tools/arch/x86/include/uapi/asm/kvm.h |  3 +++
  tools/include/uapi/linux/kvm.h        |  1 +
  13 files changed, 54 insertions(+), 6 deletions(-)
Documentation/virt/kvm/api.rst +21 -0
index 98459999273c..d2baa05f7c04 100644
@@ -147,10 +147,31 @@ described as 'basic' will be available.
  The new VM has no virtual cpus and no memory.
  You probably want to use 0 as machine type.
 
+X86:
+^^^^
+
+Supported vm type can be queried from KVM_CAP_VM_TYPES, which returns the
+bitmap of supported vm types. The 1-setting of bit @n means vm type with
+value @n is supported.
+
+S390:
+^^^^^
+
  In order to create user controlled virtual machines on S390, check
  KVM_CAP_S390_UCONTROL and use the flag KVM_VM_S390_UCONTROL as
  privileged user (CAP_SYS_ADMIN).
 
+MIPS:
+^^^^^
+
+To use hardware assisted virtualization on MIPS (VZ ASE) rather than
+the default trap & emulate implementation (which changes the virtual
+memory layout to fit in user mode), check KVM_CAP_MIPS_VZ and use the
+flag KVM_VM_MIPS_VZ.
+
+ARM64:
+^^^^^^
+
  On arm64, the physical address size for a VM (IPA Size limit) is limited
  to 40bits by default. The limit can be configured if the host supports the
  extension KVM_CAP_ARM_VM_IPA_SIZE. When supported, use
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index dba2909e5ae2..59181b12ad70 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 +2 -0
index 60dc8f1631de..c6ccfce7dc9e 100644
@@ -1212,6 +1212,7 @@ enum kvm_apicv_inhibit {
  };
 
  struct kvm_arch {
+	unsigned long vm_type;
  	unsigned long n_used_mmu_pages;
  	unsigned long n_requested_mmu_pages;
  	unsigned long n_max_mmu_pages;
@@ -1536,6 +1537,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 +3 -0
index e48deab8901d..a4cca6bc6b06 100644
@@ -529,4 +529,7 @@ struct kvm_pmu_event_filter {
  #define KVM_VCPU_TSC_CTRL 0 /* control group for the timestamp counter (TSC) */
  #define   KVM_VCPU_TSC_OFFSET 0 /* attribute for the TSC offset */
 
+#define KVM_X86_DEFAULT_VM	0
+#define KVM_X86_TDX_VM		1
+
  #endif /* _ASM_X86_KVM_H */
arch/x86/kvm/svm/svm.c +6 -0
index 799b24801d31..55f2e0a9b0f6 100644
@@ -4682,6 +4682,11 @@ static void svm_vm_destroy(struct kvm *kvm)
  	sev_vm_destroy(kvm);
  }
 
+static bool svm_is_vm_type_supported(unsigned long type)
+{
+	return type == KVM_X86_DEFAULT_VM;
+}
+
  static int svm_vm_init(struct kvm *kvm)
  {
  	if (!pause_filter_count || !pause_filter_thresh)
@@ -4710,6 +4715,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/main.c +1 -0
index f5d1166d2718..3b24e32077d6 100644
@@ -34,6 +34,7 @@ struct kvm_x86_ops vt_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/vmx/tdx.h +1 -5
index 060bf48ec3d6..473013265bd8 100644
@@ -15,11 +15,7 @@ struct vcpu_tdx {
 
  static inline bool is_td(struct kvm *kvm)
  {
-	/*
-	 * TDX VM type isn't defined yet.
-	 * return kvm->arch.vm_type == KVM_X86_TDX_VM;
-	 */
-	return false;
+	return kvm->arch.vm_type == KVM_X86_TDX_VM;
  }
 
  static inline bool is_td_vcpu(struct kvm_vcpu *vcpu)
arch/x86/kvm/vmx/vmx.c +5 -0
index 5dc7687dcf16..f1dea386d6c2 100644
@@ -7501,6 +7501,11 @@ int vmx_vcpu_create(struct kvm_vcpu *vcpu)
  	return err;
  }
 
+bool vmx_is_vm_type_supported(unsigned long type)
+{
+	return type == KVM_X86_DEFAULT_VM;
+}
+
  #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"
 
arch/x86/kvm/vmx/x86_ops.h +1 -0
index fbc57fcbdd21..6980126bc32a 100644
@@ -32,6 +32,7 @@ void vmx_hardware_unsetup(void);
  int vmx_check_processor_compat(void);
  int vmx_hardware_enable(void);
  void vmx_hardware_disable(void);
+bool vmx_is_vm_type_supported(unsigned long type);
  int vmx_vm_init(struct kvm *kvm);
  void vmx_vm_destroy(struct kvm *kvm);
  int vmx_vcpu_precreate(struct kvm *kvm);
arch/x86/kvm/x86.c +8 -1
index 07e8ab791e37..68bff699096a 100644
@@ -4535,6 +4535,11 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
  	case KVM_CAP_X86_NOTIFY_VMEXIT:
  		r = kvm_caps.has_notify_vmexit;
  		break;
+	case KVM_CAP_VM_TYPES:
+		r = BIT(KVM_X86_DEFAULT_VM);
+		if (static_call(kvm_x86_is_vm_type_supported)(KVM_X86_TDX_VM))
+			r |= BIT(KVM_X86_TDX_VM);
+		break;
  	default:
  		break;
  	}
@@ -12126,9 +12131,11 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
  	int ret;
  	unsigned long flags;
 
-	if (type)
+	if (!static_call(kvm_x86_is_vm_type_supported)(type))
  		return -EINVAL;
 
+	kvm->arch.vm_type = type;
+
  	ret = kvm_page_track_init(kvm);
  	if (ret)
  		goto out;
include/uapi/linux/kvm.h +1 -0
index 679d293ece0f..2a47fd0e51fd 100644
@@ -1212,6 +1212,7 @@ struct kvm_ppc_resize_hpt {
  #define KVM_CAP_S390_PROTECTED_ASYNC_DISABLE 224
  #define KVM_CAP_DIRTY_LOG_RING_WITH_BITMAP 225
  #define KVM_CAP_MEMORY_ATTRIBUTES 226
+#define KVM_CAP_VM_TYPES 227
 
  #ifdef KVM_CAP_IRQ_ROUTING
 
tools/arch/x86/include/uapi/asm/kvm.h +3 -0
index 649e50a8f9dd..b67d2d59eb6c 100644
@@ -524,4 +524,7 @@ struct kvm_pmu_event_filter {
  #define KVM_VCPU_TSC_CTRL 0 /* control group for the timestamp counter (TSC) */
  #define   KVM_VCPU_TSC_OFFSET 0 /* attribute for the TSC offset */
 
+#define KVM_X86_DEFAULT_VM	0
+#define KVM_X86_TDX_VM		1
+
  #endif /* _ASM_X86_KVM_H */
tools/include/uapi/linux/kvm.h +1 -0
index 20522d4ba1e0..792a4889d1f4 100644
@@ -1175,6 +1175,7 @@ struct kvm_ppc_resize_hpt {
  #define KVM_CAP_DIRTY_LOG_RING_ACQ_REL 223
  #define KVM_CAP_S390_PROTECTED_ASYNC_DISABLE 224
  #define KVM_CAP_DIRTY_LOG_RING_WITH_BITMAP 225
+#define KVM_CAP_VM_TYPES 227
 
  #ifdef KVM_CAP_IRQ_ROUTING
 
#11 lore raw

PATCH v11 6/113 KVM: TDX: Make TDX VM type supported

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

NOTE: This patch is in position of the patch series for developers to be able to test codes during the middle of the patch series although this patch series doesn't provide functional features until the all the patches of this patch series. When merging this patch series, this patch can be moved to the end.

As first step TDX VM support, return that TDX VM type supported to device model, e.g. qemu. The callback to create guest TD is vm_init callback for KVM_CREATE_VM.

---
 arch/x86/kvm/vmx/main.c    | 18 ++++++++++++++++--
 arch/x86/kvm/vmx/tdx.c     |  6 ++++++
 arch/x86/kvm/vmx/vmx.c     |  5 -----
 arch/x86/kvm/vmx/x86_ops.h |  3 ++-
 4 files changed, 24 insertions(+), 8 deletions(-)
arch/x86/kvm/vmx/main.c +16 -2
index 3b24e32077d6..e3c5e9250990 100644
@@ -10,6 +10,12 @@
 static bool enable_tdx __ro_after_init = IS_ENABLED(CONFIG_INTEL_TDX_HOST);
 module_param_named(tdx, enable_tdx, bool, 0444);
 
+static bool vt_is_vm_type_supported(unsigned long type)
+{
+	return type == KVM_X86_DEFAULT_VM ||
+		(enable_tdx && tdx_is_vm_type_supported(type));
+}
+
 static __init int vt_hardware_setup(void)
 {
 	int ret;
@@ -23,6 +29,14 @@ static __init int vt_hardware_setup(void)
 	return 0;
 }
 
+static int vt_vm_init(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		return -EOPNOTSUPP;	/* Not ready to create guest TD yet. */
+
+	return vmx_vm_init(kvm);
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
@@ -34,9 +48,9 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.hardware_disable = vmx_hardware_disable,
 	.has_emulated_msr = vmx_has_emulated_msr,
 
-	.is_vm_type_supported = vmx_is_vm_type_supported,
+	.is_vm_type_supported = vt_is_vm_type_supported,
 	.vm_size = sizeof(struct kvm_vmx),
-	.vm_init = vmx_vm_init,
+	.vm_init = vt_vm_init,
 	.vm_destroy = vmx_vm_destroy,
 
 	.vcpu_precreate = vmx_vcpu_precreate,
arch/x86/kvm/vmx/tdx.c +6 -0
index d7a276118940..6c7d9ec53046 100644
@@ -25,6 +25,12 @@ static int __init tdx_module_setup(void)
 	return 0;
 }
 
+bool tdx_is_vm_type_supported(unsigned long type)
+{
+	/* enable_tdx check is done by the caller. */
+	return type == KVM_X86_TDX_VM;
+}
+
 int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops)
 {
 	int r;
arch/x86/kvm/vmx/vmx.c +0 -5
index f1dea386d6c2..5dc7687dcf16 100644
@@ -7501,11 +7501,6 @@ int vmx_vcpu_create(struct kvm_vcpu *vcpu)
 	return err;
 }
 
-bool vmx_is_vm_type_supported(unsigned long type)
-{
-	return type == KVM_X86_DEFAULT_VM;
-}
-
 #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"
 
arch/x86/kvm/vmx/x86_ops.h +2 -1
index 6980126bc32a..8fd34842a06b 100644
@@ -32,7 +32,6 @@ void vmx_hardware_unsetup(void);
 int vmx_check_processor_compat(void);
 int vmx_hardware_enable(void);
 void vmx_hardware_disable(void);
-bool vmx_is_vm_type_supported(unsigned long type);
 int vmx_vm_init(struct kvm *kvm);
 void vmx_vm_destroy(struct kvm *kvm);
 int vmx_vcpu_precreate(struct kvm *kvm);
@@ -140,8 +139,10 @@ void vmx_setup_mce(struct kvm_vcpu *vcpu);
 
 #ifdef CONFIG_INTEL_TDX_HOST
 int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops);
+bool tdx_is_vm_type_supported(unsigned long type);
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
+static inline bool tdx_is_vm_type_supported(unsigned long type) { return false; }
 #endif
 
 #endif /* __KVM_X86_VMX_X86_OPS_H */
Signature · 1 line
2.25.1
#12 lore raw

PATCH v11 7/113 The start of TDX KVM patch series: TDX architectural definitions

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

This empty commit is to mark the start of patch series of TDX architectural definitions.

---
 .../virt/kvm/intel-tdx-layer-status.rst       | 28 +++++++++++++++++++
 1 file changed, 28 insertions(+)
 create mode 100644 Documentation/virt/kvm/intel-tdx-layer-status.rst
Documentation/virt/kvm/intel-tdx-layer-status.rst new file +28 -0
new file mode 100644
index 000000000000..db32e89e16e9
@@ -0,0 +1,28 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===================================
+Intel Trust Dodmain Extensions(TDX)
+===================================
+
+Layer status
+============
+What qemu can do
+----------------
+- TDX VM TYPE is exposed to Qemu.
+- Qemu can try to create VM of TDX VM type and then fails.
+
+Patch Layer status
+------------------
+  Patch layer                          Status
+* TDX, VMX coexistence:                 Applied
+* TDX architectural definitions:        Applying
+* TD VM creation/destruction:           Not yet
+* TD vcpu creation/destruction:         Not yet
+* TDX EPT violation:                    Not yet
+* TD finalization:                      Not yet
+* TD vcpu enter/exit:                   Not yet
+* TD vcpu interrupts/exit/hypercall:    Not yet
+
+* KVM MMU GPA shared bits:              Not yet
+* KVM TDP refactoring for TDX:          Not yet
+* KVM TDP MMU hooks:                    Not yet
Signature · 1 line
2.25.1
#13 lore raw

PATCH v11 8/113 KVM: TDX: Define TDX architectural definitions

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Define architectural definitions for KVM to issue the TDX SEAMCALLs.

Structures and values that are architecturally defined in the TDX module specifications the chapter of ABI Reference.

---
 arch/x86/kvm/vmx/tdx_arch.h | 166 ++++++++++++++++++++++++++++++++++++
 1 file changed, 166 insertions(+)
 create mode 100644 arch/x86/kvm/vmx/tdx_arch.h
arch/x86/kvm/vmx/tdx_arch.h new file +166 -0
new file mode 100644
index 000000000000..18604734fb14
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* architectural constants/data definitions for TDX SEAMCALLs */
+
+#ifndef __KVM_X86_TDX_ARCH_H
+#define __KVM_X86_TDX_ARCH_H
+
+#include <linux/types.h>
+
+/*
+ * TDX SEAMCALL API function leaves
+ */
+#define TDH_VP_ENTER			0
+#define TDH_MNG_ADDCX			1
+#define TDH_MEM_PAGE_ADD		2
+#define TDH_MEM_SEPT_ADD		3
+#define TDH_VP_ADDCX			4
+#define TDH_MEM_PAGE_RELOCATE		5
+#define TDH_MEM_PAGE_AUG		6
+#define TDH_MEM_RANGE_BLOCK		7
+#define TDH_MNG_KEY_CONFIG		8
+#define TDH_MNG_CREATE			9
+#define TDH_VP_CREATE			10
+#define TDH_MNG_RD			11
+#define TDH_MR_EXTEND			16
+#define TDH_MR_FINALIZE			17
+#define TDH_VP_FLUSH			18
+#define TDH_MNG_VPFLUSHDONE		19
+#define TDH_MNG_KEY_FREEID		20
+#define TDH_MNG_INIT			21
+#define TDH_VP_INIT			22
+#define TDH_VP_RD			26
+#define TDH_MNG_KEY_RECLAIMID		27
+#define TDH_PHYMEM_PAGE_RECLAIM		28
+#define TDH_MEM_PAGE_REMOVE		29
+#define TDH_MEM_SEPT_REMOVE		30
+#define TDH_MEM_TRACK			38
+#define TDH_MEM_RANGE_UNBLOCK		39
+#define TDH_PHYMEM_CACHE_WB		40
+#define TDH_PHYMEM_PAGE_WBINVD		41
+#define TDH_VP_WR			43
+#define TDH_SYS_LP_SHUTDOWN		44
+
+#define TDG_VP_VMCALL_GET_TD_VM_CALL_INFO		0x10000
+#define TDG_VP_VMCALL_MAP_GPA				0x10001
+#define TDG_VP_VMCALL_GET_QUOTE				0x10002
+#define TDG_VP_VMCALL_REPORT_FATAL_ERROR		0x10003
+#define TDG_VP_VMCALL_SETUP_EVENT_NOTIFY_INTERRUPT	0x10004
+
+/* TDX control structure (TDR/TDCS/TDVPS) field access codes */
+#define TDX_NON_ARCH			BIT_ULL(63)
+#define TDX_CLASS_SHIFT			56
+#define TDX_FIELD_MASK			GENMASK_ULL(31, 0)
+
+#define __BUILD_TDX_FIELD(non_arch, class, field)	\
+	(((non_arch) ? TDX_NON_ARCH : 0) |		\
+	 ((u64)(class) << TDX_CLASS_SHIFT) |		\
+	 ((u64)(field) & TDX_FIELD_MASK))
+
+#define BUILD_TDX_FIELD(class, field)			\
+	__BUILD_TDX_FIELD(false, (class), (field))
+
+#define BUILD_TDX_FIELD_NON_ARCH(class, field)		\
+	__BUILD_TDX_FIELD(true, (class), (field))
+
+
+/* Class code for TD */
+#define TD_CLASS_EXECUTION_CONTROLS	17ULL
+
+/* Class code for TDVPS */
+#define TDVPS_CLASS_VMCS		0ULL
+#define TDVPS_CLASS_GUEST_GPR		16ULL
+#define TDVPS_CLASS_OTHER_GUEST		17ULL
+#define TDVPS_CLASS_MANAGEMENT		32ULL
+
+enum tdx_tdcs_execution_control {
+	TD_TDCS_EXEC_TSC_OFFSET = 10,
+};
+
+/* @field is any of enum tdx_tdcs_execution_control */
+#define TDCS_EXEC(field)		BUILD_TDX_FIELD(TD_CLASS_EXECUTION_CONTROLS, (field))
+
+/* @field is the VMCS field encoding */
+#define TDVPS_VMCS(field)		BUILD_TDX_FIELD(TDVPS_CLASS_VMCS, (field))
+
+enum tdx_vcpu_guest_other_state {
+	TD_VCPU_STATE_DETAILS_NON_ARCH = 0x100,
+};
+
+union tdx_vcpu_state_details {
+	struct {
+		u64 vmxip	: 1;
+		u64 reserved	: 63;
+	};
+	u64 full;
+};
+
+/* @field is any of enum tdx_guest_other_state */
+#define TDVPS_STATE(field)		BUILD_TDX_FIELD(TDVPS_CLASS_OTHER_GUEST, (field))
+#define TDVPS_STATE_NON_ARCH(field)	BUILD_TDX_FIELD_NON_ARCH(TDVPS_CLASS_OTHER_GUEST, (field))
+
+/* Management class fields */
+enum tdx_vcpu_guest_management {
+	TD_VCPU_PEND_NMI = 11,
+};
+
+/* @field is any of enum tdx_vcpu_guest_management */
+#define TDVPS_MANAGEMENT(field)		BUILD_TDX_FIELD(TDVPS_CLASS_MANAGEMENT, (field))
+
+#define TDX_EXTENDMR_CHUNKSIZE		256
+
+struct tdx_cpuid_value {
+	u32 eax;
+	u32 ebx;
+	u32 ecx;
+	u32 edx;
+} __packed;
+
+#define TDX_TD_ATTRIBUTE_DEBUG		BIT_ULL(0)
+#define TDX_TD_ATTRIBUTE_PKS		BIT_ULL(30)
+#define TDX_TD_ATTRIBUTE_KL		BIT_ULL(31)
+#define TDX_TD_ATTRIBUTE_PERFMON	BIT_ULL(63)
+
+/*
+ * TD_PARAMS is provided as an input to TDH_MNG_INIT, the size of which is 1024B.
+ */
+struct td_params {
+	u64 attributes;
+	u64 xfam;
+	u32 max_vcpus;
+	u32 reserved0;
+
+	u64 eptp_controls;
+	u64 exec_controls;
+	u16 tsc_frequency;
+	u8  reserved1[38];
+
+	u64 mrconfigid[6];
+	u64 mrowner[6];
+	u64 mrownerconfig[6];
+	u64 reserved2[4];
+
+	union {
+		struct tdx_cpuid_value cpuid_values[0];
+		u8 reserved3[768];
+	};
+} __packed __aligned(1024);
+
+/*
+ * Guest uses MAX_PA for GPAW when set.
+ * 0: GPA.SHARED bit is GPA[47]
+ * 1: GPA.SHARED bit is GPA[51]
+ */
+#define TDX_EXEC_CONTROL_MAX_GPAW      BIT_ULL(0)
+
+/*
+ * TDX requires the frequency to be defined in units of 25MHz, which is the
+ * frequency of the core crystal clock on TDX-capable platforms, i.e. the TDX
+ * module can only program frequencies that are multiples of 25MHz.  The
+ * frequency must be between 100mhz and 10ghz (inclusive).
+ */
+#define TDX_TSC_KHZ_TO_25MHZ(tsc_in_khz)	((tsc_in_khz) / (25 * 1000))
+#define TDX_TSC_25MHZ_TO_KHZ(tsc_in_25mhz)	((tsc_in_25mhz) * (25 * 1000))
+#define TDX_MIN_TSC_FREQUENCY_KHZ		(100 * 1000)
+#define TDX_MAX_TSC_FREQUENCY_KHZ		(10 * 1000 * 1000)
+
+#endif /* __KVM_X86_TDX_ARCH_H */
Signature · 1 line
2.25.1
#14 lore raw

PATCH v11 9/113 KVM: TDX: Add TDX "architectural" error codes

In reply to [email protected]

From: Sean Christopherson <[email protected]>

Add error codes for the TDX SEAMCALLs both for TDX VMM side for TDH SEAMCALL and TDX guest side for TDG.VP.VMCALL. KVM issues the TDX SEAMCALLs and checks its error code. KVM handles hypercall from the TDX guest and may return an error. So error code for the TDX guest is also needed.

TDX SEAMCALL uses bits 31:0 to return more information, so these error codes will only exactly match RAX[63:32]. Error codes for TDG.VP.VMCALL is defined by TDX Guest-Host-Communication interface spec.

---
 arch/x86/kvm/vmx/tdx_errno.h | 38 ++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)
 create mode 100644 arch/x86/kvm/vmx/tdx_errno.h
arch/x86/kvm/vmx/tdx_errno.h new file +38 -0
new file mode 100644
index 000000000000..389b1b53da25
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* architectural status code for SEAMCALL */
+
+#ifndef __KVM_X86_TDX_ERRNO_H
+#define __KVM_X86_TDX_ERRNO_H
+
+#define TDX_SEAMCALL_STATUS_MASK		0xFFFFFFFF00000000ULL
+
+/*
+ * TDX SEAMCALL Status Codes (returned in RAX)
+ */
+#define TDX_SUCCESS				0x0000000000000000ULL
+#define TDX_NON_RECOVERABLE_VCPU		0x4000000100000000ULL
+#define TDX_INTERRUPTED_RESUMABLE		0x8000000300000000ULL
+#define TDX_OPERAND_BUSY			0x8000020000000000ULL
+#define TDX_VCPU_NOT_ASSOCIATED			0x8000070200000000ULL
+#define TDX_KEY_GENERATION_FAILED		0x8000080000000000ULL
+#define TDX_KEY_STATE_INCORRECT			0xC000081100000000ULL
+#define TDX_KEY_CONFIGURED			0x0000081500000000ULL
+#define TDX_NO_HKID_READY_TO_WBCACHE		0x0000082100000000ULL
+#define TDX_EPT_WALK_FAILED			0xC0000B0000000000ULL
+
+/*
+ * TDG.VP.VMCALL Status Codes (returned in R10)
+ */
+#define TDG_VP_VMCALL_SUCCESS			0x0000000000000000ULL
+#define TDG_VP_VMCALL_RETRY			0x0000000000000001ULL
+#define TDG_VP_VMCALL_INVALID_OPERAND		0x8000000000000000ULL
+#define TDG_VP_VMCALL_TDREPORT_FAILED		0x8000000000000001ULL
+
+/*
+ * TDX module operand ID, appears in 31:0 part of error code as
+ * detail information
+ */
+#define TDX_OPERAND_ID_RCX			0x01
+#define TDX_OPERAND_ID_SEPT			0x92
+
+#endif /* __KVM_X86_TDX_ERRNO_H */
Signature · 1 line
2.25.1
#15 lore raw

PATCH v11 10/113 KVM: TDX: Add C wrapper functions for SEAMCALLs to the TDX module

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

A VMM interacts with the TDX module using a new instruction (SEAMCALL). A TDX VMM uses SEAMCALLs where a VMX VMM would have directly interacted with VMX instructions. For instance, a TDX VMM does not have full access to the VM control structure corresponding to VMX VMCS. Instead, a VMM induces the TDX module to act on behalf via SEAMCALLs.

Export __seamcall and define C wrapper functions for SEAMCALLs for readability.

Some SEAMCALL APIs donates host pages to TDX module or guest TD and the donated pages are encrypted. Some of such SEAMCALLs flush cache lines (typically by movdir64b instruction), some don't. Those that doesn't clear cache lines require the VMM to flush the cache lines to avoid cache line alias.

---
 arch/x86/include/asm/tdx.h       |   2 +
 arch/x86/kvm/vmx/tdx_ops.h       | 185 +++++++++++++++++++++++++++++++
 arch/x86/virt/vmx/tdx/seamcall.S |   2 +
 3 files changed, 189 insertions(+)
 create mode 100644 arch/x86/kvm/vmx/tdx_ops.h
arch/x86/include/asm/tdx.h +2 -0
index 5c5ecfddb15b..0f71d3856ede 100644
@@ -107,6 +107,8 @@ static inline long tdx_kvm_hypercall(unsigned int nr, unsigned long p1,
 #ifdef CONFIG_INTEL_TDX_HOST
 bool platform_tdx_enabled(void);
 int tdx_enable(void);
+u64 __seamcall(u64 op, u64 rcx, u64 rdx, u64 r8, u64 r9,
+	       struct tdx_module_output *out);
 #else	/* !CONFIG_INTEL_TDX_HOST */
 static inline bool platform_tdx_enabled(void) { return false; }
 static inline int tdx_enable(void)  { return -EINVAL; }
arch/x86/kvm/vmx/tdx_ops.h new file +185 -0

185 lines not on this page · raw message

arch/x86/virt/vmx/tdx/seamcall.S +2 -0
index f81be6b9c133..b90a7fe05494 100644
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 #include <linux/linkage.h>
+#include <asm/export.h>
 #include <asm/frame.h>
 
 #include "tdxcall.S"
@@ -50,3 +51,4 @@ SYM_FUNC_START(__seamcall)
 	FRAME_END
 	RET
 SYM_FUNC_END(__seamcall)
+EXPORT_SYMBOL_GPL(__seamcall)
Signature · 1 line
2.25.1
#16 lore raw

PATCH v11 11/113 KVM: TDX: Add helper functions to print TDX SEAMCALL error

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Add helper functions to print out errors from the TDX module in a uniform manner.

---
 arch/x86/kvm/Makefile        |  2 +-
 arch/x86/kvm/vmx/tdx_error.c | 21 +++++++++++++++++++++
 arch/x86/kvm/vmx/tdx_ops.h   |  3 +++
 3 files changed, 25 insertions(+), 1 deletion(-)
 create mode 100644 arch/x86/kvm/vmx/tdx_error.c
arch/x86/kvm/Makefile +1 -1
index 4b01ab842ab7..e3354b784e10 100644
@@ -25,7 +25,7 @@ kvm-$(CONFIG_KVM_SMM)	+= smm.o
 kvm-intel-y		+= vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o vmx/vmcs12.o \
 			   vmx/hyperv.o vmx/nested.o vmx/posted_intr.o vmx/main.o
 kvm-intel-$(CONFIG_X86_SGX_KVM)	+= vmx/sgx.o
-kvm-intel-$(CONFIG_INTEL_TDX_HOST)	+= vmx/tdx.o
+kvm-intel-$(CONFIG_INTEL_TDX_HOST)	+= vmx/tdx.o vmx/tdx_error.o
 
 kvm-amd-y		+= svm/svm.o svm/vmenter.o svm/pmu.o svm/nested.o svm/avic.o \
 			   svm/sev.o svm/hyperv.o
arch/x86/kvm/vmx/tdx_error.c new file +21 -0
new file mode 100644
index 000000000000..574b72d34e1e
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+/* functions to record TDX SEAMCALL error */
+
+#include <linux/kernel.h>
+#include <linux/bug.h>
+
+#include "tdx_ops.h"
+
+void pr_tdx_error(u64 op, u64 error_code, const struct tdx_module_output *out)
+{
+	if (!out) {
+		pr_err_ratelimited("SEAMCALL[%lld] failed: 0x%llx\n",
+				   op, error_code);
+		return;
+	}
+
+	pr_err_ratelimited("SEAMCALL[%lld] failed: 0x%llx RCX 0x%llx, RDX 0x%llx,"
+			   " R8 0x%llx, R9 0x%llx, R10 0x%llx, R11 0x%llx\n",
+			   op, error_code,
+			   out->rcx, out->rdx, out->r8, out->r9, out->r10, out->r11);
+}
arch/x86/kvm/vmx/tdx_ops.h +3 -0
index 85adbf49c277..8cc2f01c509b 100644
@@ -9,12 +9,15 @@
 #include <asm/cacheflush.h>
 #include <asm/asm.h>
 #include <asm/kvm_host.h>
+#include <asm/tdx.h>
 
 #include "tdx_errno.h"
 #include "tdx_arch.h"
 
 #ifdef CONFIG_INTEL_TDX_HOST
 
+void pr_tdx_error(u64 op, u64 error_code, const struct tdx_module_output *out);
+
 static inline u64 tdh_mng_addcx(hpa_t tdr, hpa_t addr)
 {
 	clflush_cache_range(__va(addr), PAGE_SIZE);
Signature · 1 line
2.25.1
#17 lore raw

PATCH v11 12/113 The start of TDX KVM patch series: TD VM creation/destruction

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

This empty commit is to mark the start of patch series of TD VM creation/destruction.

---
 Documentation/virt/kvm/intel-tdx-layer-status.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Documentation/virt/kvm/intel-tdx-layer-status.rst +2 -2
index db32e89e16e9..221372cfb4af 100644
@@ -15,8 +15,8 @@ Patch Layer status
 ------------------
   Patch layer                          Status
 * TDX, VMX coexistence:                 Applied
-* TDX architectural definitions:        Applying
-* TD VM creation/destruction:           Not yet
+* TDX architectural definitions:        Applied
+* TD VM creation/destruction:           Applying
 * TD vcpu creation/destruction:         Not yet
 * TDX EPT violation:                    Not yet
 * TD finalization:                      Not yet
Signature · 1 line
2.25.1
#18 lore raw

PATCH v11 13/113 x86/cpu: Add helper functions to allocate/free TDX private host key id

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TDX private host key id (HKID) is assigned to guest TD. The memory controller encrypts guest TD memory with the assigned TDX HKID. Add helper functions to allocate/free TDX private HKID so that TDX KVM can manage it.

Also export the global TDX private HKID that is used to encrypt TDX module, its memory and some dynamic data (TDR). When VMM releasing encrypted page to reuse it, the page needs to be flushed with the used HKID. VMM needs the global TDX private HKID to flush such pages.

---
 arch/x86/include/asm/tdx.h  | 12 ++++++++++++
 arch/x86/virt/vmx/tdx/tdx.c | 35 +++++++++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+)
arch/x86/include/asm/tdx.h +12 -0
index 0f71d3856ede..ed9cf61ff8b4 100644
@@ -107,11 +107,23 @@ static inline long tdx_kvm_hypercall(unsigned int nr, unsigned long p1,
 #ifdef CONFIG_INTEL_TDX_HOST
 bool platform_tdx_enabled(void);
 int tdx_enable(void);
+/*
+ * Key id globally used by TDX module: TDX module maps TDR with this TDX global
+ * key id.  TDR includes key id assigned to the TD.  Then TDX module maps other
+ * TD-related pages with the assigned key id.  TDR requires this TDX global key
+ * id for cache flush unlike other TD-related pages.
+ */
+extern u32 tdx_global_keyid __read_mostly;
+int tdx_keyid_alloc(void);
+void tdx_keyid_free(int keyid);
+
 u64 __seamcall(u64 op, u64 rcx, u64 rdx, u64 r8, u64 r9,
 	       struct tdx_module_output *out);
 #else	/* !CONFIG_INTEL_TDX_HOST */
 static inline bool platform_tdx_enabled(void) { return false; }
 static inline int tdx_enable(void)  { return -EINVAL; }
+static inline int tdx_keyid_alloc(void) { return -EOPNOTSUPP; }
+static inline void tdx_keyid_free(int keyid) { }
 #endif	/* CONFIG_INTEL_TDX_HOST */
 
 #endif /* !__ASSEMBLY__ */
arch/x86/virt/vmx/tdx/tdx.c +35 -0
index eba7e62cebec..d18ab5c4d447 100644
@@ -51,6 +51,10 @@ static DEFINE_MUTEX(tdx_module_lock);
 /* All TDX-usable memory regions */
 static LIST_HEAD(tdx_memlist);
 
+/* TDX module global KeyID.  Used in TDH.SYS.CONFIG ABI. */
+u32 tdx_global_keyid __read_mostly;
+EXPORT_SYMBOL_GPL(tdx_global_keyid);
+
 /*
  * tdx_keyid_start and nr_tdx_keyids indicate that TDX is uninitialized.
  * This is used in TDX initialization error paths to take it from
@@ -132,6 +136,31 @@ static struct notifier_block tdx_memory_nb = {
 	.notifier_call = tdx_memory_notifier,
 };
 
+/* TDX KeyID pool */
+static DEFINE_IDA(tdx_keyid_pool);
+
+int tdx_keyid_alloc(void)
+{
+	if (WARN_ON_ONCE(!tdx_keyid_start || !nr_tdx_keyids))
+		return -EINVAL;
+
+	/* The first keyID is reserved for the global key. */
+	return ida_alloc_range(&tdx_keyid_pool, tdx_keyid_start + 1,
+			       tdx_keyid_start + nr_tdx_keyids - 1,
+			       GFP_KERNEL);
+}
+EXPORT_SYMBOL_GPL(tdx_keyid_alloc);
+
+void tdx_keyid_free(int keyid)
+{
+	/* keyid = 0 is reserved. */
+	if (WARN_ON_ONCE(keyid <= 0))
+		return;
+
+	ida_free(&tdx_keyid_pool, keyid);
+}
+EXPORT_SYMBOL_GPL(tdx_keyid_free);
+
 static int __init tdx_init(void)
 {
 	int err;
@@ -1161,6 +1190,12 @@ static int init_tdx_module(void)
 	if (ret)
 		goto out_free_pamts;
 
+	/*
+	 * Reserve the first TDX KeyID as global KeyID to protect
+	 * TDX module metadata.
+	 */
+	tdx_global_keyid = tdx_keyid_start;
+
 	/* Initialize TDMRs to complete the TDX module initialization */
 	ret = init_tdmrs(&tdmr_list);
 	if (ret)
Signature · 1 line
2.25.1
#19 lore raw

Re: [PATCH v11 013/113] x86/cpu: Add helper functions to allocate/free TDX private host key id

Quoted 65 lines · On Thu, 12 Jan 2023 08:31:21 -0800 [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX private host key id (HKID) is assigned to guest TD. The memory controller encrypts guest TD memory with the assigned TDX HKID. Add helper functions to allocate/free TDX private HKID so that TDX KVM can manage it.

Also export the global TDX private HKID that is used to encrypt TDX module, its memory and some dynamic data (TDR). When VMM releasing encrypted page to reuse it, the page needs to be flushed with the used HKID. VMM needs the global TDX private HKID to flush such pages.

---
 arch/x86/include/asm/tdx.h  | 12 ++++++++++++
 arch/x86/virt/vmx/tdx/tdx.c | 35 +++++++++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+)
arch/x86/include/asm/tdx.h +0 -0
index 0f71d3856ede..ed9cf61ff8b4 100644
@@ -107,11 +107,23 @@ static inline long tdx_kvm_hypercall(unsigned int

nr, unsigned long p1, #ifdef CONFIG_INTEL_TDX_HOST
bool platform_tdx_enabled(void);
int tdx_enable(void);
+/*
+ * Key id globally used by TDX module: TDX module maps TDR with this TDX global
+ * key id. TDR includes key id assigned to the TD. Then TDX module maps other
+ * TD-related pages with the assigned key id. TDR requires this TDX global key
+ * id for cache flush unlike other TD-related pages.
+ */

arch/x86/include/asm/tdx.h +6 -0
+extern u32 tdx_global_keyid __read_mostly;
+int tdx_keyid_alloc(void);
+void tdx_keyid_free(int keyid);
+
 u64 __seamcall(u64 op, u64 rcx, u64 rdx, u64 r8, u64 r9,
 	       struct tdx_module_output *out);
 #else	/* !CONFIG_INTEL_TDX_HOST */
 static inline bool platform_tdx_enabled(void) { return false; }
 static inline int tdx_enable(void)  { return -EINVAL; }
+static inline int tdx_keyid_alloc(void) { return -EOPNOTSUPP; }
+static inline void tdx_keyid_free(int keyid) { }
 #endif	/* CONFIG_INTEL_TDX_HOST */
 
 #endif /* !__ASSEMBLY__ */
arch/x86/virt/vmx/tdx/tdx.c +4 -0
index eba7e62cebec..d18ab5c4d447 100644
@@ -51,6 +51,10 @@ static DEFINE_MUTEX(tdx_module_lock);
 /* All TDX-usable memory regions */
 static LIST_HEAD(tdx_memlist);
 
+/* TDX module global KeyID.  Used in TDH.SYS.CONFIG ABI. */
+u32 tdx_global_keyid __read_mostly;
+EXPORT_SYMBOL_GPL(tdx_global_keyid);
+
 /*
  * tdx_keyid_start and nr_tdx_keyids indicate that TDX is uninitialized.
  * This is used in TDX initialization error paths to take it from

arch/x86/virt/vmx/tdx/tdx.c static struct notifier_block tdx_memory_nb = { · line 145

Quoted 10 lines
arch/x86/virt/vmx/tdx/tdx.c +7 -0
@@ -132,6 +136,31 @@ static struct notifier_block tdx_memory_nb = {
 	.notifier_call = tdx_memory_notifier,
 };
 
+/* TDX KeyID pool */
+static DEFINE_IDA(tdx_keyid_pool);
+
+int tdx_keyid_alloc(void)
+{
+	if (WARN_ON_ONCE(!tdx_keyid_start || !nr_tdx_keyids))
+		return -EINVAL;

Better mention that tdx_keyid_start and nr_tdx_keyids are defined in another patches.

Quoted 34 lines
arch/x86/virt/vmx/tdx/tdx.c +24 -0
+
+	/* The first keyID is reserved for the global key. */
+	return ida_alloc_range(&tdx_keyid_pool, tdx_keyid_start + 1,
+			       tdx_keyid_start + nr_tdx_keyids - 1,
+			       GFP_KERNEL);
+}
+EXPORT_SYMBOL_GPL(tdx_keyid_alloc);
+
+void tdx_keyid_free(int keyid)
+{
+	/* keyid = 0 is reserved. */
+	if (WARN_ON_ONCE(keyid <= 0))
+		return;
+
+	ida_free(&tdx_keyid_pool, keyid);
+}
+EXPORT_SYMBOL_GPL(tdx_keyid_free);
+
 static int __init tdx_init(void)
 {
 	int err;
@@ -1161,6 +1190,12 @@ static int init_tdx_module(void)
 	if (ret)
 		goto out_free_pamts;
 
+	/*
+	 * Reserve the first TDX KeyID as global KeyID to protect
+	 * TDX module metadata.
+	 */
+	tdx_global_keyid = tdx_keyid_start;
+
 	/* Initialize TDMRs to complete the TDX module initialization */
 	ret = init_tdmrs(&tdmr_list);
 	if (ret)
#20 lore raw

Re: [PATCH v11 013/113] x86/cpu: Add helper functions to allocate/free TDX private host key id

Quoted 5 lines · Zhi Wang
Quoted 4 lines · On Thu, 12 Jan 2023 08:31:21 -0800 [email protected] wrote:
arch/x86/virt/vmx/tdx/tdx.c +0 -0
@@ -132,6 +136,31 @@ static struct notifier_block tdx_memory_nb = {
 	.notifier_call = tdx_memory_notifier,
 };
 
Quoted 10 lines

arch/x86/virt/vmx/tdx/tdx.c static struct notifier_block tdx_memory_nb = { · line 145

Quoted 7 lines
arch/x86/virt/vmx/tdx/tdx.c +7 -0
+/* TDX KeyID pool */
+static DEFINE_IDA(tdx_keyid_pool);
+
+int tdx_keyid_alloc(void)
+{
+	if (WARN_ON_ONCE(!tdx_keyid_start || !nr_tdx_keyids))
+		return -EINVAL;

Better mention that tdx_keyid_start and nr_tdx_keyids are defined in another patches.

Eh, no need. That sort of information doesn't belong in the changelog because when this code is merged it will be a natural sequence. The cover letter explicitly calls out that this needs the kernel patches[*]. A footnote could be added, but asking Isaku and co. to document every external dependency is asking too much IMO.

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

#21 lore raw

Re: [PATCH v11 013/113] x86/cpu: Add helper functions to allocate/free TDX private host key id

Quoted 17 lines · On Fri, 13 Jan 2023 15:21:54 +0000 Sean Christopherson <[email protected]>…
Quoted 5 lines · Zhi Wang
Quoted 4 lines · On Thu, 12 Jan 2023 08:31:21 -0800 [email protected] wrote:
arch/x86/virt/vmx/tdx/tdx.c +0 -0
@@ -132,6 +136,31 @@ static struct notifier_block tdx_memory_nb = {
 	.notifier_call = tdx_memory_notifier,
 };
 
Quoted 10 lines

arch/x86/virt/vmx/tdx/tdx.c static struct notifier_block tdx_memory_nb = { · line 145

Quoted 7 lines
arch/x86/virt/vmx/tdx/tdx.c +7 -0
+/* TDX KeyID pool */
+static DEFINE_IDA(tdx_keyid_pool);
+
+int tdx_keyid_alloc(void)
+{
+	if (WARN_ON_ONCE(!tdx_keyid_start || !nr_tdx_keyids))
+		return -EINVAL;

Better mention that tdx_keyid_start and nr_tdx_keyids are defined in another patches.

Quoted 7 lines

Eh, no need. That sort of information doesn't belong in the changelog because when this code is merged it will be a natural sequence. The cover letter explicitly calls out that this needs the kernel patches[*].
A footnote could be added, but asking Isaku and co. to document every
external dependency is asking too much IMO.

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

Hi:

Thanks. I raised this concern from the reviewers' perspective. For example, finding something was missing, grep, nothing was found, and jumping to another window and grep.

Finally, you can make sure if missing tdx_keyid_start in the patch is a mistake or a dependency. Then the same happens on nr_tdx_keyids.

It would be nice to just say tdx_hkid_start, nr_tdx_keyid requires an external patch in the comment. Or, just mention this patch depends on an external patch in the comment. It will save quite some efforts.

#22 lore raw

PATCH v11 14/113 x86/virt/tdx: Add a helper function to return system wide info about TDX module

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TDX KVM needs system-wide information about the TDX module, struct tdsysinfo_struct. Add a helper function tdx_get_sysinfo() to return it instead of KVM getting it with various error checks. Make KVM call the function and stash the info. Move out the struct definition about it to common place arch/x86/include/asm/tdx.h.

---
 arch/x86/include/asm/tdx.h  | 54 +++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/tdx.c      | 49 ++++++++++++++++++++++++++++++++-
 arch/x86/virt/vmx/tdx/tdx.c | 21 ++++++++++++---
 arch/x86/virt/vmx/tdx/tdx.h | 51 -----------------------------------
 4 files changed, 119 insertions(+), 56 deletions(-)
arch/x86/include/asm/tdx.h +54 -0
index ed9cf61ff8b4..2ca6e8ce1e43 100644
@@ -105,6 +105,58 @@ 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
+struct tdx_cpuid_config {
+	u32	leaf;
+	u32	sub_leaf;
+	u32	eax;
+	u32	ebx;
+	u32	ecx;
+	u32	edx;
+} __packed;
+
+#define TDSYSINFO_STRUCT_SIZE		1024
+#define TDSYSINFO_STRUCT_ALIGNMENT	1024
+
+/*
+ * The size of this structure itself is flexible.  The actual structure
+ * passed to TDH.SYS.INFO must be padded to TDSYSINFO_STRUCT_SIZE and be
+ * aligned to TDSYSINFO_STRUCT_ALIGNMENT using DECLARE_PADDED_STRUCT().
+ */
+struct tdsysinfo_struct {
+	/* TDX-SEAM Module Info */
+	u32	attributes;
+	u32	vendor_id;
+	u32	build_date;
+	u16	build_num;
+	u16	minor_version;
+	u16	major_version;
+	u8	reserved0[14];
+	/* Memory Info */
+	u16	max_tdmrs;
+	u16	max_reserved_per_tdmr;
+	u16	pamt_entry_size;
+	u8	reserved1[10];
+	/* Control Struct Info */
+	u16	tdcs_base_size;
+	u8	reserved2[2];
+	u16	tdvps_base_size;
+	u8	tdvps_xfam_dependent_size;
+	u8	reserved3[9];
+	/* TD Capabilities */
+	u64	attributes_fixed0;
+	u64	attributes_fixed1;
+	u64	xfam_fixed0;
+	u64	xfam_fixed1;
+	u8	reserved4[32];
+	u32	num_cpuid_config;
+	/*
+	 * The actual number of CPUID_CONFIG depends on above
+	 * 'num_cpuid_config'.
+	 */
+	DECLARE_FLEX_ARRAY(struct tdx_cpuid_config, cpuid_configs);
+} __packed;
+
+const struct tdsysinfo_struct *tdx_get_sysinfo(void);
 bool platform_tdx_enabled(void);
 int tdx_enable(void);
 /*
@@ -120,6 +172,8 @@ void tdx_keyid_free(int keyid);
 u64 __seamcall(u64 op, u64 rcx, u64 rdx, u64 r8, u64 r9,
 	       struct tdx_module_output *out);
 #else	/* !CONFIG_INTEL_TDX_HOST */
+struct tdsysinfo_struct;
+static inline const struct tdsysinfo_struct *tdx_get_sysinfo(void) { return NULL; }
 static inline bool platform_tdx_enabled(void) { return false; }
 static inline int tdx_enable(void)  { return -EINVAL; }
 static inline int tdx_keyid_alloc(void) { return -EOPNOTSUPP; }
arch/x86/kvm/vmx/tdx.c +48 -1

63 lines not on this page · raw message

arch/x86/virt/vmx/tdx/tdx.c +17 -4
index d18ab5c4d447..65c0024fd3a9 100644
@@ -279,7 +279,7 @@ static void print_cmrs(struct cmr_info *cmr_array, int nr_cmrs)
  * kernel stack.  @sysinfo must have been padded to have enough room
  * to save the TDSYSINFO_STRUCT.
  */
-static int tdx_get_sysinfo(struct tdsysinfo_struct *sysinfo,
+static int __tdx_get_sysinfo(struct tdsysinfo_struct *sysinfo,
 			   struct cmr_info *cmr_array)
 {
 	struct tdx_module_output out;
@@ -308,6 +308,21 @@ static int tdx_get_sysinfo(struct tdsysinfo_struct *sysinfo,
 	return 0;
 }
 
+static DECLARE_PADDED_STRUCT(tdsysinfo_struct, tdsysinfo,
+			     TDSYSINFO_STRUCT_SIZE, TDSYSINFO_STRUCT_ALIGNMENT);
+
+const struct tdsysinfo_struct *tdx_get_sysinfo(void)
+{
+	const struct tdsysinfo_struct *r = NULL;
+
+	mutex_lock(&tdx_module_lock);
+	if (tdx_module_status == TDX_MODULE_INITIALIZED)
+		r = &PADDED_STRUCT(tdsysinfo);
+	mutex_unlock(&tdx_module_lock);
+	return r;
+}
+EXPORT_SYMBOL_GPL(tdx_get_sysinfo);
+
 /*
  * Add a memory region as a TDX memory block.  The caller must make sure
  * all memory regions are added in address ascending order and don't
@@ -1118,8 +1133,6 @@ static int init_tdx_module(void)
 	 * They are 1024 bytes and 512 bytes respectively but it's fine to
 	 * keep them in the stack as this function is only called once.
 	 */
-	DECLARE_PADDED_STRUCT(tdsysinfo_struct, tdsysinfo,
-			TDSYSINFO_STRUCT_SIZE, TDSYSINFO_STRUCT_ALIGNMENT);
 	struct cmr_info cmr_array[MAX_CMRS] __aligned(CMR_INFO_ARRAY_ALIGNMENT);
 	struct tdsysinfo_struct *sysinfo = &PADDED_STRUCT(tdsysinfo);
 	struct tdmr_info_list tdmr_list;
@@ -1134,7 +1147,7 @@ static int init_tdx_module(void)
 	if (ret)
 		goto out;
 
-	ret = tdx_get_sysinfo(sysinfo, cmr_array);
+	ret = __tdx_get_sysinfo(sysinfo, cmr_array);
 	if (ret)
 		goto out;
 
arch/x86/virt/vmx/tdx/tdx.h +0 -51

63 lines not on this page · raw message

Signature · 1 line
2.25.1
#23 lore raw

Re: [PATCH v11 014/113] x86/virt/tdx: Add a helper function to return system wide info about TDX module

Quoted 129 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX KVM needs system-wide information about the TDX module, struct tdsysinfo_struct. Add a helper function tdx_get_sysinfo() to return it instead of KVM getting it with various error checks. Make KVM call the function and stash the info. Move out the struct definition about it to common place arch/x86/include/asm/tdx.h.

---
 arch/x86/include/asm/tdx.h  | 54 +++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/tdx.c      | 49 ++++++++++++++++++++++++++++++++-
 arch/x86/virt/vmx/tdx/tdx.c | 21 ++++++++++++---
 arch/x86/virt/vmx/tdx/tdx.h | 51 -----------------------------------
 4 files changed, 119 insertions(+), 56 deletions(-)
arch/x86/include/asm/tdx.h +54 -0
index ed9cf61ff8b4..2ca6e8ce1e43 100644
@@ -105,6 +105,58 @@ 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
+struct tdx_cpuid_config {
+	u32	leaf;
+	u32	sub_leaf;
+	u32	eax;
+	u32	ebx;
+	u32	ecx;
+	u32	edx;
+} __packed;
+
+#define TDSYSINFO_STRUCT_SIZE		1024
+#define TDSYSINFO_STRUCT_ALIGNMENT	1024
+
+/*
+ * The size of this structure itself is flexible.  The actual structure
+ * passed to TDH.SYS.INFO must be padded to TDSYSINFO_STRUCT_SIZE and be
+ * aligned to TDSYSINFO_STRUCT_ALIGNMENT using DECLARE_PADDED_STRUCT().
+ */
+struct tdsysinfo_struct {
+	/* TDX-SEAM Module Info */
+	u32	attributes;
+	u32	vendor_id;
+	u32	build_date;
+	u16	build_num;
+	u16	minor_version;
+	u16	major_version;
+	u8	reserved0[14];
+	/* Memory Info */
+	u16	max_tdmrs;
+	u16	max_reserved_per_tdmr;
+	u16	pamt_entry_size;
+	u8	reserved1[10];
+	/* Control Struct Info */
+	u16	tdcs_base_size;
+	u8	reserved2[2];
+	u16	tdvps_base_size;
+	u8	tdvps_xfam_dependent_size;
+	u8	reserved3[9];
+	/* TD Capabilities */
+	u64	attributes_fixed0;
+	u64	attributes_fixed1;
+	u64	xfam_fixed0;
+	u64	xfam_fixed1;
+	u8	reserved4[32];
+	u32	num_cpuid_config;
+	/*
+	 * The actual number of CPUID_CONFIG depends on above
+	 * 'num_cpuid_config'.
+	 */
+	DECLARE_FLEX_ARRAY(struct tdx_cpuid_config, cpuid_configs);
+} __packed;
+
+const struct tdsysinfo_struct *tdx_get_sysinfo(void);
 bool platform_tdx_enabled(void);
 int tdx_enable(void);
 /*
@@ -120,6 +172,8 @@ void tdx_keyid_free(int keyid);
 u64 __seamcall(u64 op, u64 rcx, u64 rdx, u64 r8, u64 r9,
 	       struct tdx_module_output *out);
 #else	/* !CONFIG_INTEL_TDX_HOST */
+struct tdsysinfo_struct;
+static inline const struct tdsysinfo_struct *tdx_get_sysinfo(void) { return NULL; }
 static inline bool platform_tdx_enabled(void) { return false; }
 static inline int tdx_enable(void)  { return -EINVAL; }
 static inline int tdx_keyid_alloc(void) { return -EOPNOTSUPP; }
arch/x86/kvm/vmx/tdx.c +26 -1
index 6c7d9ec53046..2adf5551ab26 100644
@@ -11,9 +11,34 @@
 #undef pr_fmt
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
+#define TDX_MAX_NR_CPUID_CONFIGS					\
+	((TDSYSINFO_STRUCT_SIZE -					\
+		offsetof(struct tdsysinfo_struct, cpuid_configs))	\
+		/ sizeof(struct tdx_cpuid_config))
+
+struct tdx_capabilities {
+	u8 tdcs_nr_pages;
+	u8 tdvpx_nr_pages;
+
+	u64 attrs_fixed0;
+	u64 attrs_fixed1;
+	u64 xfam_fixed0;
+	u64 xfam_fixed1;
+
+	u32 nr_cpuid_configs;
+	struct tdx_cpuid_config cpuid_configs[TDX_MAX_NR_CPUID_CONFIGS];
+};
+
+/* Capabilities of KVM + the TDX module. */
+static struct tdx_capabilities tdx_caps;
+
 static int __init tdx_module_setup(void)
 {
-	int ret;
+	const struct tdsysinfo_struct *tdsysinfo;
+	int ret = 0;
+
+	BUILD_BUG_ON(sizeof(*tdsysinfo) > TDSYSINFO_STRUCT_SIZE);
+	BUILD_BUG_ON(TDX_MAX_NR_CPUID_CONFIGS != 37);
 
 	ret = tdx_enable();
 	if (ret) {

arch/x86/kvm/vmx/tdx.c static int __init tdx_module_setup(void) · line 51

Quoted 6 lines
arch/x86/kvm/vmx/tdx.c +3 -0
@@ -21,6 +46,28 @@ static int __init tdx_module_setup(void)
 		return ret;
 	}
 
+	tdsysinfo = tdx_get_sysinfo();
+	if (tdsysinfo->num_cpuid_config > TDX_MAX_NR_CPUID_CONFIGS)
+		return -EIO;

This check basically means TDX module is buggy (or kernel has bug). Do we really need this check? Is TDX module that buggy?

IMHO you don't need this one, or use WARN() if you want to catch kernel bug?

Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +8 -0
+
+	tdx_caps = (struct tdx_capabilities) {
+		.tdcs_nr_pages = tdsysinfo->tdcs_base_size / PAGE_SIZE,
+		/*
+		 * TDVPS = TDVPR(4K page) + TDVPX(multiple 4K pages).
+		 * -1 for TDVPR.
+		 */
+		.tdvpx_nr_pages = tdsysinfo->tdvps_base_size / PAGE_SIZE - 1,

arch/x86/kvm/vmx/tdx.c static int __init tdx_module_setup(void) · line 69

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		.attrs_fixed0 = tdsysinfo->attributes_fixed0,
+		.attrs_fixed1 = tdsysinfo->attributes_fixed1,
+		.xfam_fixed0 =	tdsysinfo->xfam_fixed0,
+		.xfam_fixed1 = tdsysinfo->xfam_fixed1,
+		.nr_cpuid_configs = tdsysinfo->num_cpuid_config,
+	};
+	if (!memcpy(tdx_caps.cpuid_configs, tdsysinfo->cpuid_configs,
+			tdsysinfo->num_cpuid_config *
+			sizeof(struct tdx_cpuid_config)))
+		return -EIO;

Why introducing 'struct tdx_capabilities' and above code here in this patch?

It's entirely not clear why the new structure is needed -- nothing mentioned in changelog, nor there's any comment. Please explain in the changelog or move this chunk to where it is needed.

Technical side, is 'struct tdx_capabilities' really needed? Or is it just for convenience?

[snip]

#24 lore raw

Re: [PATCH v11 014/113] x86/virt/tdx: Add a helper function to return system wide info about TDX module

Quoted 133 lines · Kai
Quoted 132 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX KVM needs system-wide information about the TDX module, struct tdsysinfo_struct. Add a helper function tdx_get_sysinfo() to return it instead of KVM getting it with various error checks. Make KVM call the function and stash the info. Move out the struct definition about it to common place arch/x86/include/asm/tdx.h.

---
 arch/x86/include/asm/tdx.h  | 54 +++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/tdx.c      | 49 ++++++++++++++++++++++++++++++++-
 arch/x86/virt/vmx/tdx/tdx.c | 21 ++++++++++++---
 arch/x86/virt/vmx/tdx/tdx.h | 51 -----------------------------------
 4 files changed, 119 insertions(+), 56 deletions(-)
arch/x86/include/asm/tdx.h +54 -0
index ed9cf61ff8b4..2ca6e8ce1e43 100644
@@ -105,6 +105,58 @@ 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
+struct tdx_cpuid_config {
+	u32	leaf;
+	u32	sub_leaf;
+	u32	eax;
+	u32	ebx;
+	u32	ecx;
+	u32	edx;
+} __packed;
+
+#define TDSYSINFO_STRUCT_SIZE		1024
+#define TDSYSINFO_STRUCT_ALIGNMENT	1024
+
+/*
+ * The size of this structure itself is flexible.  The actual structure
+ * passed to TDH.SYS.INFO must be padded to TDSYSINFO_STRUCT_SIZE and be
+ * aligned to TDSYSINFO_STRUCT_ALIGNMENT using DECLARE_PADDED_STRUCT().
+ */
+struct tdsysinfo_struct {
+	/* TDX-SEAM Module Info */
+	u32	attributes;
+	u32	vendor_id;
+	u32	build_date;
+	u16	build_num;
+	u16	minor_version;
+	u16	major_version;
+	u8	reserved0[14];
+	/* Memory Info */
+	u16	max_tdmrs;
+	u16	max_reserved_per_tdmr;
+	u16	pamt_entry_size;
+	u8	reserved1[10];
+	/* Control Struct Info */
+	u16	tdcs_base_size;
+	u8	reserved2[2];
+	u16	tdvps_base_size;
+	u8	tdvps_xfam_dependent_size;
+	u8	reserved3[9];
+	/* TD Capabilities */
+	u64	attributes_fixed0;
+	u64	attributes_fixed1;
+	u64	xfam_fixed0;
+	u64	xfam_fixed1;
+	u8	reserved4[32];
+	u32	num_cpuid_config;
+	/*
+	 * The actual number of CPUID_CONFIG depends on above
+	 * 'num_cpuid_config'.
+	 */
+	DECLARE_FLEX_ARRAY(struct tdx_cpuid_config, cpuid_configs);
+} __packed;
+
+const struct tdsysinfo_struct *tdx_get_sysinfo(void);
 bool platform_tdx_enabled(void);
 int tdx_enable(void);
 /*
@@ -120,6 +172,8 @@ void tdx_keyid_free(int keyid);
 u64 __seamcall(u64 op, u64 rcx, u64 rdx, u64 r8, u64 r9,
 	       struct tdx_module_output *out);
 #else	/* !CONFIG_INTEL_TDX_HOST */
+struct tdsysinfo_struct;
+static inline const struct tdsysinfo_struct *tdx_get_sysinfo(void) { return NULL; }
 static inline bool platform_tdx_enabled(void) { return false; }
 static inline int tdx_enable(void)  { return -EINVAL; }
 static inline int tdx_keyid_alloc(void) { return -EOPNOTSUPP; }
arch/x86/kvm/vmx/tdx.c +26 -1
index 6c7d9ec53046..2adf5551ab26 100644
@@ -11,9 +11,34 @@
 #undef pr_fmt
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
+#define TDX_MAX_NR_CPUID_CONFIGS					\
+	((TDSYSINFO_STRUCT_SIZE -					\
+		offsetof(struct tdsysinfo_struct, cpuid_configs))	\
+		/ sizeof(struct tdx_cpuid_config))
+
+struct tdx_capabilities {
+	u8 tdcs_nr_pages;
+	u8 tdvpx_nr_pages;
+
+	u64 attrs_fixed0;
+	u64 attrs_fixed1;
+	u64 xfam_fixed0;
+	u64 xfam_fixed1;
+
+	u32 nr_cpuid_configs;
+	struct tdx_cpuid_config cpuid_configs[TDX_MAX_NR_CPUID_CONFIGS];
+};
+
+/* Capabilities of KVM + the TDX module. */
+static struct tdx_capabilities tdx_caps;
+
 static int __init tdx_module_setup(void)
 {
-	int ret;
+	const struct tdsysinfo_struct *tdsysinfo;
+	int ret = 0;
+
+	BUILD_BUG_ON(sizeof(*tdsysinfo) > TDSYSINFO_STRUCT_SIZE);
+	BUILD_BUG_ON(TDX_MAX_NR_CPUID_CONFIGS != 37);
 
 	ret = tdx_enable();
 	if (ret) {
@@ -21,6 +46,28 @@ static int __init tdx_module_setup(void)
 		return ret;
 	}
 
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c static int __init tdx_module_setup(void) · line 51

Quoted 3 lines
arch/x86/kvm/vmx/tdx.c +3 -0
+	tdsysinfo = tdx_get_sysinfo();
+	if (tdsysinfo->num_cpuid_config > TDX_MAX_NR_CPUID_CONFIGS)
+		return -EIO;

This check basically means TDX module is buggy (or kernel has bug). Do we really need this check? Is TDX module that buggy?

IMHO you don't need this one, or use WARN() if you want to catch kernel bug?

Ok, I made it WARN_ON().

Quoted 21 lines
Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +8 -0
+
+	tdx_caps = (struct tdx_capabilities) {
+		.tdcs_nr_pages = tdsysinfo->tdcs_base_size / PAGE_SIZE,
+		/*
+		 * TDVPS = TDVPR(4K page) + TDVPX(multiple 4K pages).
+		 * -1 for TDVPR.
+		 */
+		.tdvpx_nr_pages = tdsysinfo->tdvps_base_size / PAGE_SIZE - 1,

arch/x86/kvm/vmx/tdx.c static int __init tdx_module_setup(void) · line 69

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		.attrs_fixed0 = tdsysinfo->attributes_fixed0,
+		.attrs_fixed1 = tdsysinfo->attributes_fixed1,
+		.xfam_fixed0 =	tdsysinfo->xfam_fixed0,
+		.xfam_fixed1 = tdsysinfo->xfam_fixed1,
+		.nr_cpuid_configs = tdsysinfo->num_cpuid_config,
+	};
+	if (!memcpy(tdx_caps.cpuid_configs, tdsysinfo->cpuid_configs,
+			tdsysinfo->num_cpuid_config *
+			sizeof(struct tdx_cpuid_config)))
+		return -EIO;

Why introducing 'struct tdx_capabilities' and above code here in this patch?

Quoted 6 lines

It's entirely not clear why the new structure is needed -- nothing mentioned in changelog, nor there's any comment. Please explain in the changelog or move this chunk to where it is needed.

Technical side, is 'struct tdx_capabilities' really needed? Or is it just for convenience?

I made use of tdsysinfo where possible.
Because it's convenient to cache the number of tdcs_pages and tdvps_pages, it renamed tdx_capabilities into tdx_info to keep those two. and Move it to the patch that uses the value.

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

PATCH v11 15/113 KVM: TDX: x86: Add ioctl to get TDX systemwide parameters

In reply to [email protected]

From: Sean Christopherson <[email protected]>

Implement a system-scoped ioctl to get system-wide parameters for TDX.

---
 arch/x86/include/asm/kvm-x86-ops.h    |  1 +
 arch/x86/include/asm/kvm_host.h       |  1 +
 arch/x86/include/uapi/asm/kvm.h       | 48 +++++++++++++++++++++++++++
 arch/x86/kvm/vmx/main.c               |  2 ++
 arch/x86/kvm/vmx/tdx.c                | 46 +++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h            |  2 ++
 arch/x86/kvm/x86.c                    |  6 ++++
 tools/arch/x86/include/uapi/asm/kvm.h | 48 +++++++++++++++++++++++++++
 8 files changed, 154 insertions(+)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 59181b12ad70..e6708bb3f4f6 100644
@@ -118,6 +118,7 @@ KVM_X86_OP(enter_smm)
 KVM_X86_OP(leave_smm)
 KVM_X86_OP(enable_smi_window)
 #endif
+KVM_X86_OP_OPTIONAL(dev_mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_register_region)
 KVM_X86_OP_OPTIONAL(mem_enc_unregister_region)
arch/x86/include/asm/kvm_host.h +1 -0
index c6ccfce7dc9e..234c28c8e6ee 100644
@@ -1693,6 +1693,7 @@ struct kvm_x86_ops {
 	void (*enable_smi_window)(struct kvm_vcpu *vcpu);
 #endif
 
+	int (*dev_mem_enc_ioctl)(void __user *argp);
 	int (*mem_enc_ioctl)(struct kvm *kvm, void __user *argp);
 	int (*mem_enc_register_region)(struct kvm *kvm, struct kvm_enc_region *argp);
 	int (*mem_enc_unregister_region)(struct kvm *kvm, struct kvm_enc_region *argp);
arch/x86/include/uapi/asm/kvm.h +48 -0

52 lines not on this page · raw message

arch/x86/kvm/vmx/main.c +2 -0
index e3c5e9250990..16053ec3e0ae 100644
@@ -177,6 +177,8 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.complete_emulated_msr = kvm_complete_insn_gp,
 
 	.vcpu_deliver_sipi_vector = kvm_vcpu_deliver_sipi_vector,
+
+	.dev_mem_enc_ioctl = tdx_dev_ioctl,
 };
 
 struct kvm_x86_init_ops vt_init_ops __initdata = {
arch/x86/kvm/vmx/tdx.c +46 -0

52 lines not on this page · raw message

arch/x86/kvm/vmx/x86_ops.h +2 -0
index 8fd34842a06b..f0803cc15151 100644
@@ -140,9 +140,11 @@ void vmx_setup_mce(struct kvm_vcpu *vcpu);
 #ifdef CONFIG_INTEL_TDX_HOST
 int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops);
 bool tdx_is_vm_type_supported(unsigned long type);
+int tdx_dev_ioctl(void __user *argp);
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
 static inline bool tdx_is_vm_type_supported(unsigned long type) { return false; }
+static inline int tdx_dev_ioctl(void __user *argp) { return -EOPNOTSUPP; };
 #endif
 
 #endif /* __KVM_X86_VMX_X86_OPS_H */
arch/x86/kvm/x86.c +6 -0
index 68bff699096a..1152a9dc6d84 100644
@@ -4693,6 +4693,12 @@ long kvm_arch_dev_ioctl(struct file *filp,
 		r = kvm_x86_dev_has_attr(&attr);
 		break;
 	}
+	case KVM_MEMORY_ENCRYPT_OP:
+		r = -EINVAL;
+		if (!kvm_x86_ops.dev_mem_enc_ioctl)
+			goto out;
+		r = static_call(kvm_x86_dev_mem_enc_ioctl)(argp);
+		break;
 	default:
 		r = -EINVAL;
 		break;
tools/arch/x86/include/uapi/asm/kvm.h +48 -0

52 lines not on this page · raw message

Signature · 1 line
2.25.1
#26 lore raw

PATCH v11 16/113 KVM: TDX: Add place holder for TDX VM specific mem_enc_op ioctl

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Add a place holder function for TDX specific VM-scoped ioctl as mem_enc_op. TDX specific sub-commands will be added to retrieve/pass TDX specific parameters.

KVM_MEMORY_ENCRYPT_OP was introduced for VM-scoped operations specific for guest state-protected VM. It defined subcommands for technology-specific operations under KVM_MEMORY_ENCRYPT_OP. Despite its name, the subcommands are not limited to memory encryption, but various technology-specific operations are defined. It's natural to repurpose KVM_MEMORY_ENCRYPT_OP for TDX specific operations and define subcommands.

TDX requires VM-scoped TDX-specific operations for device model, for example, qemu. Getting system-wide parameters, TDX-specific VM initialization.

---
 arch/x86/kvm/vmx/main.c    |  9 +++++++++
 arch/x86/kvm/vmx/tdx.c     | 26 ++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h |  4 ++++
 3 files changed, 39 insertions(+)
arch/x86/kvm/vmx/main.c +9 -0
index 16053ec3e0ae..781fbc896120 100644
@@ -37,6 +37,14 @@ static int vt_vm_init(struct kvm *kvm)
 	return vmx_vm_init(kvm);
 }
 
+static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
+{
+	if (!is_td(kvm))
+		return -ENOTTY;
+
+	return tdx_vm_ioctl(kvm, argp);
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
@@ -179,6 +187,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.vcpu_deliver_sipi_vector = kvm_vcpu_deliver_sipi_vector,
 
 	.dev_mem_enc_ioctl = tdx_dev_ioctl,
+	.mem_enc_ioctl = vt_mem_enc_ioctl,
 };
 
 struct kvm_x86_init_ops vt_init_ops __initdata = {
arch/x86/kvm/vmx/tdx.c +26 -0
index f46579102a44..2bd1cc37abab 100644
@@ -78,6 +78,32 @@ int tdx_dev_ioctl(void __user *argp)
 	return 0;
 }
 
+int tdx_vm_ioctl(struct kvm *kvm, void __user *argp)
+{
+	struct kvm_tdx_cmd tdx_cmd;
+	int r;
+
+	if (copy_from_user(&tdx_cmd, argp, sizeof(struct kvm_tdx_cmd)))
+		return -EFAULT;
+	if (tdx_cmd.error || tdx_cmd.unused)
+		return -EINVAL;
+
+	mutex_lock(&kvm->lock);
+
+	switch (tdx_cmd.id) {
+	default:
+		r = -EINVAL;
+		goto out;
+	}
+
+	if (copy_to_user(argp, &tdx_cmd, sizeof(struct kvm_tdx_cmd)))
+		r = -EFAULT;
+
+out:
+	mutex_unlock(&kvm->lock);
+	return r;
+}
+
 static int __init tdx_module_setup(void)
 {
 	const struct tdsysinfo_struct *tdsysinfo;
arch/x86/kvm/vmx/x86_ops.h +4 -0
index f0803cc15151..bb4090dbae37 100644
@@ -141,10 +141,14 @@ void vmx_setup_mce(struct kvm_vcpu *vcpu);
 int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops);
 bool tdx_is_vm_type_supported(unsigned long type);
 int tdx_dev_ioctl(void __user *argp);
+
+int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
 static inline bool tdx_is_vm_type_supported(unsigned long type) { return false; }
 static inline int tdx_dev_ioctl(void __user *argp) { return -EOPNOTSUPP; };
+
+static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
 #endif
 
 #endif /* __KVM_X86_VMX_X86_OPS_H */
Signature · 1 line
2.25.1
#27 lore raw

Re: [PATCH v11 016/113] KVM: TDX: Add place holder for TDX VM specific mem_enc_op ioctl

Quoted 13 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Add a place holder function for TDX specific VM-scoped ioctl as mem_enc_op. TDX specific sub-commands will be added to retrieve/pass TDX specific parameters.

KVM_MEMORY_ENCRYPT_OP was introduced for VM-scoped operations specific for guest state-protected VM. It defined subcommands for technology-specific operations under KVM_MEMORY_ENCRYPT_OP. Despite its name, the subcommands are not limited to memory encryption, but various technology-specific operations are defined. It's natural to repurpose KVM_MEMORY_ENCRYPT_OP for TDX specific operations and define subcommands.

Quoted 3 lines

TDX requires VM-scoped TDX-specific operations for device model, for example, qemu. Getting system-wide parameters, TDX-specific VM initialization.

There's grammar issue in the last paragraph. Please use grammar check.

Quoted 28 lines
---
 arch/x86/kvm/vmx/main.c    |  9 +++++++++
 arch/x86/kvm/vmx/tdx.c     | 26 ++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h |  4 ++++
 3 files changed, 39 insertions(+)
arch/x86/kvm/vmx/main.c +8 -0
index 16053ec3e0ae..781fbc896120 100644
@@ -37,6 +37,14 @@ static int vt_vm_init(struct kvm *kvm)
 	return vmx_vm_init(kvm);
 }
 
+static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
+{
+	if (!is_td(kvm))
+		return -ENOTTY;
+
+	return tdx_vm_ioctl(kvm, argp);
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 

arch/x86/kvm/vmx/main.c struct kvm_x86_ops vt_x86_ops __initdata = { · line 191

Quoted 5 lines
arch/x86/kvm/vmx/main.c +1 -0
@@ -179,6 +187,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.vcpu_deliver_sipi_vector = kvm_vcpu_deliver_sipi_vector,
 
 	.dev_mem_enc_ioctl = tdx_dev_ioctl,
+	.mem_enc_ioctl = vt_mem_enc_ioctl,
 };

IIUC, now both AMD and Intel have mem_enc_ioctl() callback implemented, so the KVM_X86_OP_OPTIONAL() of it can be changed to KVM_X86_OP(), and the function pointer check can be removed in the IOCTL:

arch/x86/include/asm/kvm-x86- renamed from arch/x86/include/asm/kvm-x86-ops.h +0 -0

ops.h
index 8dc345cc6318..a59852fb5e2a 100644

arch/x86/include/asm/kvm-x86-ops.h +1 -1
@@ -116,7 +116,7 @@ KVM_X86_OP(enter_smm)
 KVM_X86_OP(leave_smm)
 KVM_X86_OP(enable_smi_window)
 #endif
-KVM_X86_OP_OPTIONAL(mem_enc_ioctl)
+KVM_X86_OP(mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_register_region)
 KVM_X86_OP_OPTIONAL(mem_enc_unregister_region)
 KVM_X86_OP_OPTIONAL(vm_copy_enc_context_from)
arch/x86/kvm/x86.c +0 -4
index c936f8d28a53..dfa279e35478 100644
@@ -6937,10 +6937,6 @@ long kvm_arch_vm_ioctl(struct file *filp,
                goto out;
        }
        case KVM_MEMORY_ENCRYPT_OP: {
-               r = -ENOTTY;
-               if (!kvm_x86_ops.mem_enc_ioctl)
-                       goto out;
-
                r = static_call(kvm_x86_mem_enc_ioctl)(kvm, argp);
                break;
        }

[snip]

#28 lore raw

Re: [PATCH v11 016/113] KVM: TDX: Add place holder for TDX VM specific mem_enc_op ioctl

Quoted 22 lines · Kai
Quoted 22 lines
arch/x86/kvm/vmx/main.c +8 -0
index 16053ec3e0ae..781fbc896120 100644
@@ -37,6 +37,14 @@ static int vt_vm_init(struct kvm *kvm)
 	return vmx_vm_init(kvm);
 }
 
+static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
+{
+	if (!is_td(kvm))
+		return -ENOTTY;
+
+	return tdx_vm_ioctl(kvm, argp);
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
@@ -179,6 +187,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.vcpu_deliver_sipi_vector = kvm_vcpu_deliver_sipi_vector,
 
Quoted 7 lines

arch/x86/kvm/vmx/main.c struct kvm_x86_ops vt_x86_ops __initdata = { · line 191

Quoted 3 lines
arch/x86/kvm/vmx/main.c +1 -0
 	.dev_mem_enc_ioctl = tdx_dev_ioctl,
+	.mem_enc_ioctl = vt_mem_enc_ioctl,
 };

IIUC, now both AMD and Intel have mem_enc_ioctl() callback implemented, so the KVM_X86_OP_OPTIONAL() of it can be changed to KVM_X86_OP(), and the function pointer check can be removed in the IOCTL:

Makes sense. Merged the following patch into the patch. Thanks for it.

Quoted 33 lines
arch/x86/include/asm/kvm-x86- renamed from arch/x86/include/asm/kvm-x86-ops.h +0 -0

ops.h
index 8dc345cc6318..a59852fb5e2a 100644

arch/x86/include/asm/kvm-x86-ops.h +1 -1
@@ -116,7 +116,7 @@ KVM_X86_OP(enter_smm)
 KVM_X86_OP(leave_smm)
 KVM_X86_OP(enable_smi_window)
 #endif
-KVM_X86_OP_OPTIONAL(mem_enc_ioctl)
+KVM_X86_OP(mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_register_region)
 KVM_X86_OP_OPTIONAL(mem_enc_unregister_region)
 KVM_X86_OP_OPTIONAL(vm_copy_enc_context_from)
arch/x86/kvm/x86.c +0 -4
index c936f8d28a53..dfa279e35478 100644
@@ -6937,10 +6937,6 @@ long kvm_arch_vm_ioctl(struct file *filp,
                goto out;
        }
        case KVM_MEMORY_ENCRYPT_OP: {
-               r = -ENOTTY;
-               if (!kvm_x86_ops.mem_enc_ioctl)
-                       goto out;
-
                r = static_call(kvm_x86_mem_enc_ioctl)(kvm, argp);
                break;
        }
 
[snip]
Signature · 1 line
Isaku Yamahata <[email protected]>
#29 lore raw

PATCH v11 17/113 KVM: Support KVM_CAP_MAX_VCPUS for KVM_ENABLE_CAP

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TDX attestation includes the maximum number of vcpu that the guest can accommodate. For that, the maximum number of vcpu needs to be specified instead of constant, KVM_MAX_VCPUS. Make KVM_ENABLE_CAP support KVM_CAP_MAX_VCPUS.

---
 virt/kvm/kvm_main.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
virt/kvm/kvm_main.c +20 -0
index a235b628b32f..1cfa7da92ad0 100644
@@ -4945,7 +4945,27 @@ static int kvm_vm_ioctl_enable_cap_generic(struct kvm *kvm,
 		}
 
 		mutex_unlock(&kvm->slots_lock);
+		return r;
+	}
+	case KVM_CAP_MAX_VCPUS: {
+		int r;
 
+		if (cap->flags || cap->args[0] == 0)
+			return -EINVAL;
+		if (cap->args[0] >  kvm_vm_ioctl_check_extension(kvm, KVM_CAP_MAX_VCPUS))
+			return -E2BIG;
+
+		mutex_lock(&kvm->lock);
+		/* Only decreasing is allowed. */
+		if (cap->args[0] > kvm->max_vcpus)
+			r = -E2BIG;
+		else if (kvm->created_vcpus)
+			r = -EBUSY;
+		else {
+			kvm->max_vcpus = cap->args[0];
+			r = 0;
+		}
+		mutex_unlock(&kvm->lock);
 		return r;
 	}
 	default:
Signature · 1 line
2.25.1
#30 lore raw

Re: [PATCH v11 017/113] KVM: Support KVM_CAP_MAX_VCPUS for KVM_ENABLE_CAP

Quoted 18 lines · On Thu, 12 Jan 2023 08:31:25 -0800 [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX attestation includes the maximum number of vcpu that the guest can accommodate. For that, the maximum number of vcpu needs to be specified instead of constant, KVM_MAX_VCPUS. Make KVM_ENABLE_CAP support KVM_CAP_MAX_VCPUS.

---
 virt/kvm/kvm_main.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
virt/kvm/kvm_main.c +0 -0
index a235b628b32f..1cfa7da92ad0 100644

virt/kvm/kvm_main.c static int kvm_vm_ioctl_enable_cap_generic(struct · line 4950

Quoted 6 lines
virt/kvm/kvm_main.c +3 -0
@@ -4945,7 +4945,27 @@ static int kvm_vm_ioctl_enable_cap_generic(struct
 kvm *kvm, }
 
 		mutex_unlock(&kvm->slots_lock);
+		return r;
+	}
+	case KVM_CAP_MAX_VCPUS: {

Better mention the KVM_CAP_MAX_VCPUS defined in XXX patch in the comments.

Quoted 22 lines
virt/kvm/kvm_main.c +17 -0
+		int r;
 
+		if (cap->flags || cap->args[0] == 0)
+			return -EINVAL;
+		if (cap->args[0] >  kvm_vm_ioctl_check_extension(kvm,
 KVM_CAP_MAX_VCPUS))
+			return -E2BIG;
+
+		mutex_lock(&kvm->lock);
+		/* Only decreasing is allowed. */
+		if (cap->args[0] > kvm->max_vcpus)
+			r = -E2BIG;
+		else if (kvm->created_vcpus)
+			r = -EBUSY;
+		else {
+			kvm->max_vcpus = cap->args[0];
+			r = 0;
+		}
+		mutex_unlock(&kvm->lock);
 		return r;
 	}

default:

#31 lore raw

Re: [PATCH v11 017/113] KVM: Support KVM_CAP_MAX_VCPUS for KVM_ENABLE_CAP

Quoted 23 lines · Zhi Wang
Quoted 20 lines · On Thu, 12 Jan 2023 08:31:25 -0800 [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX attestation includes the maximum number of vcpu that the guest can accommodate. For that, the maximum number of vcpu needs to be specified instead of constant, KVM_MAX_VCPUS. Make KVM_ENABLE_CAP support KVM_CAP_MAX_VCPUS.

---
 virt/kvm/kvm_main.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
virt/kvm/kvm_main.c +0 -0
index a235b628b32f..1cfa7da92ad0 100644
@@ -4945,7 +4945,27 @@ static int kvm_vm_ioctl_enable_cap_generic(struct
 kvm *kvm, }
 
Quoted 6 lines

virt/kvm/kvm_main.c static int kvm_vm_ioctl_enable_cap_generic(struct kvm *kvm, · line 4950

Quoted 4 lines
virt/kvm/kvm_main.c +3 -0
 		mutex_unlock(&kvm->slots_lock);
+		return r;
+	}
+	case KVM_CAP_MAX_VCPUS: {

Better mention the KVM_CAP_MAX_VCPUS defined in XXX patch in the comments.

This already exists as KVM api and documented in Documentation/virt/kvm/api.rst to get the possible maximum number of vcpus.
This patch make it settable.

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

Re: [PATCH v11 017/113] KVM: Support KVM_CAP_MAX_VCPUS for KVM_ENABLE_CAP

In reply to [email protected]
Quoted 5 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX attestation includes the maximum number of vcpu that the guest can accommodate.

I don't understand why "attestation" is the reason here. Let's say TDX is used w/o attestation, I don't think this patch can be discarded?

IMHO the true reason is TDX has it's own control of maximum number of vcpus, i.e. asking you to specify the value when creating the TD. Therefore, the constant KVM_MAX_VCPUS doesn't work for TDX guest anymore.

Quoted 20 lines

For that, the maximum number of vcpu needs to be specified instead of constant, KVM_MAX_VCPUS. Make KVM_ENABLE_CAP support KVM_CAP_MAX_VCPUS.

---
 virt/kvm/kvm_main.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
virt/kvm/kvm_main.c +2 -0
index a235b628b32f..1cfa7da92ad0 100644
@@ -4945,7 +4945,27 @@ static int kvm_vm_ioctl_enable_cap_generic(struct kvm *kvm,
 		}
 
 		mutex_unlock(&kvm->slots_lock);
+		return r;
+	}

virt/kvm/kvm_main.c static int kvm_vm_ioctl_enable_cap_generic(struct kvm *kvm, · line 4959

Quoted 10 lines
virt/kvm/kvm_main.c +9 -0
+	case KVM_CAP_MAX_VCPUS: {
+		int r;
 
+		if (cap->flags || cap->args[0] == 0)
+			return -EINVAL;
+		if (cap->args[0] >  kvm_vm_ioctl_check_extension(kvm, KVM_CAP_MAX_VCPUS))
+			return -E2BIG;
+
+		mutex_lock(&kvm->lock);
+		/* Only decreasing is allowed. */

Why?

virt/kvm/kvm_main.c static int kvm_vm_ioctl_enable_cap_generic(struct kvm *kvm, · line 4971

Quoted 12 lines
virt/kvm/kvm_main.c +9 -0
+		if (cap->args[0] > kvm->max_vcpus)
+			r = -E2BIG;
+		else if (kvm->created_vcpus)
+			r = -EBUSY;
+		else {
+			kvm->max_vcpus = cap->args[0];
+			r = 0;
+		}
+		mutex_unlock(&kvm->lock);
 		return r;
 	}
 	default:

Also, IIUC this change is made to the generic kvm_main.c, which means other archs are affected too. Is this OK to other archs? Why such change cannot TDX-specific (or, at least x86, or vmx specific)?

#33 lore raw

Re: [PATCH v11 017/113] KVM: Support KVM_CAP_MAX_VCPUS for KVM_ENABLE_CAP

Quoted 7 lines · Kai
Quoted 5 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX attestation includes the maximum number of vcpu that the guest can accommodate.

Quoted 6 lines

I don't understand why "attestation" is the reason here. Let's say TDX is used w/o attestation, I don't think this patch can be discarded?

IMHO the true reason is TDX has it's own control of maximum number of vcpus, i.e. asking you to specify the value when creating the TD. Therefore, the constant KVM_MAX_VCPUS doesn't work for TDX guest anymore.

Without TDX attestation, this can be discarded. The TD is created with max_vcpus=KVM_MAX_VCPUS by default.

Quoted 25 lines
Quoted 25 lines

For that, the maximum number of vcpu needs to be specified instead of constant, KVM_MAX_VCPUS. Make KVM_ENABLE_CAP support KVM_CAP_MAX_VCPUS.

---
 virt/kvm/kvm_main.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
virt/kvm/kvm_main.c +4 -0
index a235b628b32f..1cfa7da92ad0 100644
@@ -4945,7 +4945,27 @@ static int kvm_vm_ioctl_enable_cap_generic(struct kvm *kvm,
 		}
 
 		mutex_unlock(&kvm->slots_lock);
+		return r;
+	}
+	case KVM_CAP_MAX_VCPUS: {
+		int r;
 
Quoted 9 lines

virt/kvm/kvm_main.c static int kvm_vm_ioctl_enable_cap_generic(struct kvm *kvm, · line 4959

Quoted 7 lines
virt/kvm/kvm_main.c +7 -0
+		if (cap->flags || cap->args[0] == 0)
+			return -EINVAL;
+		if (cap->args[0] >  kvm_vm_ioctl_check_extension(kvm, KVM_CAP_MAX_VCPUS))
+			return -E2BIG;
+
+		mutex_lock(&kvm->lock);
+		/* Only decreasing is allowed. */

Why?

I'll make it x86 specific and will drop this check.

Quoted 8 lines
Quoted 8 lines
virt/kvm/kvm_main.c +8 -0
+		if (cap->args[0] > kvm->max_vcpus)
+			r = -E2BIG;
+		else if (kvm->created_vcpus)
+			r = -EBUSY;
+		else {
+			kvm->max_vcpus = cap->args[0];
+			r = 0;
+		}
Quoted 8 lines

virt/kvm/kvm_main.c static int kvm_vm_ioctl_enable_cap_generic(struct kvm *kvm, · line 4971

Quoted 4 lines
virt/kvm/kvm_main.c +1 -0
+		mutex_unlock(&kvm->lock);
 		return r;
 	}
 	default:

Also, IIUC this change is made to the generic kvm_main.c, which means other archs are affected too. Is this OK to other archs? Why such change cannot TDX-specific (or, at least x86, or vmx specific)?

Ok, I made it x86 specific.

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

Re: [PATCH v11 017/113] KVM: Support KVM_CAP_MAX_VCPUS for KVM_ENABLE_CAP

Quoted 8 lines · Isaku Yamahata
Quoted 8 lines
Quoted 3 lines

TDX attestation includes the maximum number of vcpu that the guest can accommodate.

I don't understand why "attestation" is the reason here.  Let's say TDX is used
w/o attestation, I don't think this patch can be discarded?

Quoted 6 lines
Quoted 3 lines

IMHO the true reason is TDX has it's own control of maximum number of vcpus, i.e. asking you to specify the value when creating the TD.  Therefore, the constant KVM_MAX_VCPUS doesn't work for TDX guest anymore.

Without TDX attestation, this can be discarded.  The TD is created with max_vcpus=KVM_MAX_VCPUS by default.

This parses like:

If we have attestation, the TD can be created with a user-specified non-default value. Otherwise, the TD is always created with default value.

It doesn't make sense, right?

Because architecturally whether TD can be created with a user specified value doesn't depend on attestation at all.

#35 lore raw

Re: [PATCH v11 017/113] KVM: Support KVM_CAP_MAX_VCPUS for KVM_ENABLE_CAP

Quoted 18 lines · Kai
Quoted 8 lines · Isaku Yamahata
Quoted 8 lines
Quoted 3 lines

TDX attestation includes the maximum number of vcpu that the guest can accommodate.

I don't understand why "attestation" is the reason here.  Let's say TDX is used
w/o attestation, I don't think this patch can be discarded?

Quoted 6 lines
Quoted 3 lines

IMHO the true reason is TDX has it's own control of maximum number of vcpus, i.e. asking you to specify the value when creating the TD.  Therefore, the constant KVM_MAX_VCPUS doesn't work for TDX guest anymore.

Without TDX attestation, this can be discarded.  The TD is created with max_vcpus=KVM_MAX_VCPUS by default.

This parses like:

Quoted 7 lines

If we have attestation, the TD can be created with a user-specified non-default value. Otherwise, the TD is always created with default value.

It doesn't make sense, right?

Because architecturally whether TD can be created with a user specified value doesn't depend on attestation at all.

I'm not sure if I got your point.
Even without attestation, it's allowed to specify max vcpus. Not "always".

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

Re: [PATCH v11 017/113] KVM: Support KVM_CAP_MAX_VCPUS for KVM_ENABLE_CAP

Quoted 24 lines · Isaku Yamahata
Quoted 21 lines · Kai
Quoted 8 lines · Isaku Yamahata
Quoted 8 lines
Quoted 3 lines

TDX attestation includes the maximum number of vcpu that the guest can accommodate.

I don't understand why "attestation" is the reason here.  Let's say TDX is used
w/o attestation, I don't think this patch can be discarded?

Quoted 6 lines
Quoted 3 lines

IMHO the true reason is TDX has it's own control of maximum number of vcpus, i.e. asking you to specify the value when creating the TD.  Therefore, the constant KVM_MAX_VCPUS doesn't work for TDX guest anymore.

Without TDX attestation, this can be discarded.  The TD is created with max_vcpus=KVM_MAX_VCPUS by default.

This parses like:

If we have attestation, the TD can be created with a user-specified non-default value. Otherwise, the TD is always created with default value.

Quoted 8 lines
Quoted 4 lines

It doesn't make sense, right?

Because architecturally whether TD can be created with a user specified value doesn't depend on attestation at all.

I'm not sure if I got your point.
Even without attestation, it's allowed to specify max vcpus. Not "always".

Exactly.

So "allow to specify max vcpus" isn't due to attestation, as you also said.

Then why this patch is due to "TDX attestation includes the maximum number of vcpu that the guest can accommodate"? Shouldn't the reason be "TDX architecturally has it's own control of maximum number of vcpus" as I mentioned in my first reply?

#37 lore raw

PATCH v11 18/113 KVM: TDX: create/destroy VM structure

In reply to [email protected]

From: Sean Christopherson <[email protected]>

As the first step to create TDX guest, create/destroy VM struct. Assign TDX private Host Key ID (HKID) to the TDX guest for memory encryption and allocate extra pages for the TDX guest. On destruction, free allocated pages, and HKID.

Before tearing down private page tables, TDX requires some resources of the guest TD to be destroyed (i.e. HKID must have been reclaimed, etc). Add flush_shadow_all_private callback before tearing down private page tables for it.

Add vm_free() of kvm_x86_ops hook at the end of kvm_arch_destroy_vm() because some per-VM TDX resources, e.g. TDR, need to be freed after other TDX resources, e.g. HKID, were freed.

---
Changes v10 -> v11:
- Fix doule free in tdx_vm_free() by setting NULL.
- replace struct tdx_td_page tdr and tdcs from struct kvm_tdx with unsigned long

---
 arch/x86/include/asm/kvm-x86-ops.h |   2 +
 arch/x86/include/asm/kvm_host.h    |   2 +
 arch/x86/kvm/vmx/main.c            |  34 ++-
 arch/x86/kvm/vmx/tdx.c             | 415 +++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/tdx.h             |   6 +-
 arch/x86/kvm/vmx/x86_ops.h         |   9 +
 arch/x86/kvm/x86.c                 |   8 +
 7 files changed, 472 insertions(+), 4 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +2 -0
index e6708bb3f4f6..552de893af75 100644
@@ -22,7 +22,9 @@ 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(flush_shadow_all_private)
 KVM_X86_OP_OPTIONAL(vm_destroy)
+KVM_X86_OP_OPTIONAL(vm_free)
 KVM_X86_OP_OPTIONAL_RET0(vcpu_precreate)
 KVM_X86_OP(vcpu_create)
 KVM_X86_OP(vcpu_free)
arch/x86/include/asm/kvm_host.h +2 -0
index 234c28c8e6ee..e199ddf0bb00 100644
@@ -1540,7 +1540,9 @@ struct kvm_x86_ops {
 	bool (*is_vm_type_supported)(unsigned long vm_type);
 	unsigned int vm_size;
 	int (*vm_init)(struct kvm *kvm);
+	void (*flush_shadow_all_private)(struct kvm *kvm);
 	void (*vm_destroy)(struct kvm *kvm);
+	void (*vm_free)(struct kvm *kvm);
 
 	/* Create, but do not attach this VCPU */
 	int (*vcpu_precreate)(struct kvm *kvm);
arch/x86/kvm/vmx/main.c +31 -3
index 781fbc896120..c5f2515026e9 100644
@@ -29,14 +29,40 @@ static __init int vt_hardware_setup(void)
 	return 0;
 }
 
+static void vt_hardware_unsetup(void)
+{
+	tdx_hardware_unsetup();
+	vmx_hardware_unsetup();
+}
+
 static int vt_vm_init(struct kvm *kvm)
 {
 	if (is_td(kvm))
-		return -EOPNOTSUPP;	/* Not ready to create guest TD yet. */
+		return tdx_vm_init(kvm);
 
 	return vmx_vm_init(kvm);
 }
 
+static void vt_flush_shadow_all_private(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		tdx_mmu_release_hkid(kvm);
+}
+
+static void vt_vm_destroy(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		return;
+
+	vmx_vm_destroy(kvm);
+}
+
+static void vt_vm_free(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		tdx_vm_free(kvm);
+}
+
 static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 {
 	if (!is_td(kvm))
@@ -50,7 +76,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.check_processor_compatibility = vmx_check_processor_compat,
 
-	.hardware_unsetup = vmx_hardware_unsetup,
+	.hardware_unsetup = vt_hardware_unsetup,
 
 	.hardware_enable = vmx_hardware_enable,
 	.hardware_disable = vmx_hardware_disable,
@@ -59,7 +85,9 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.is_vm_type_supported = vt_is_vm_type_supported,
 	.vm_size = sizeof(struct kvm_vmx),
 	.vm_init = vt_vm_init,
-	.vm_destroy = vmx_vm_destroy,
+	.flush_shadow_all_private = vt_flush_shadow_all_private,
+	.vm_destroy = vt_vm_destroy,
+	.vm_free = vt_vm_free,
 
 	.vcpu_precreate = vmx_vcpu_precreate,
 	.vcpu_create = vmx_vcpu_create,
arch/x86/kvm/vmx/tdx.c +415 -0

448 lines not on this page · raw message

arch/x86/kvm/vmx/tdx.h +5 -1
index 473013265bd8..e78d72cf4c3a 100644
@@ -5,7 +5,11 @@
 #ifdef CONFIG_INTEL_TDX_HOST
 struct kvm_tdx {
 	struct kvm kvm;
-	/* TDX specific members follow. */
+
+	unsigned long tdr_pa;
+	unsigned long *tdcs_pa;
+
+	int hkid;
 };
 
 struct vcpu_tdx {
arch/x86/kvm/vmx/x86_ops.h +9 -0
index bb4090dbae37..3d0f519727c6 100644
@@ -139,15 +139,24 @@ void vmx_setup_mce(struct kvm_vcpu *vcpu);
 
 #ifdef CONFIG_INTEL_TDX_HOST
 int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops);
+void tdx_hardware_unsetup(void);
 bool tdx_is_vm_type_supported(unsigned long type);
 int tdx_dev_ioctl(void __user *argp);
 
+int tdx_vm_init(struct kvm *kvm);
+void tdx_mmu_release_hkid(struct kvm *kvm);
+void tdx_vm_free(struct kvm *kvm);
 int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
+static inline void tdx_hardware_unsetup(void) {}
 static inline bool tdx_is_vm_type_supported(unsigned long type) { return false; }
 static inline int tdx_dev_ioctl(void __user *argp) { return -EOPNOTSUPP; };
 
+static inline int tdx_vm_init(struct kvm *kvm) { return -EOPNOTSUPP; }
+static inline void tdx_mmu_release_hkid(struct kvm *kvm) {}
+static inline void tdx_flush_shadow_all_private(struct kvm *kvm) {}
+static inline void tdx_vm_free(struct kvm *kvm) {}
 static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
 #endif
 
arch/x86/kvm/x86.c +8 -0
index 1152a9dc6d84..0fa91a9708aa 100644
@@ -12337,6 +12337,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
 	kvm_page_track_cleanup(kvm);
 	kvm_xen_destroy_vm(kvm);
 	kvm_hv_destroy_vm(kvm);
+	static_call_cond(kvm_x86_vm_free)(kvm);
 }
 
 static void memslot_rmap_free(struct kvm_memory_slot *slot)
@@ -12647,6 +12648,13 @@ void kvm_arch_commit_memory_region(struct kvm *kvm,
 
 void kvm_arch_flush_shadow_all(struct kvm *kvm)
 {
+	/*
+	 * kvm_mmu_zap_all() zaps both private and shared page tables.  Before
+	 * tearing down private page tables, TDX requires some TD resources to
+	 * be destroyed (i.e. keyID must have been reclaimed, etc).  Invoke
+	 * kvm_x86_flush_shadow_all_private() for this.
+	 */
+	static_call_cond(kvm_x86_flush_shadow_all_private)(kvm);
 	kvm_mmu_zap_all(kvm);
 }
 
Signature · 1 line
2.25.1
#38 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 262 lines · On Thu, 12 Jan 2023 08:31:26 -0800 [email protected] wrote:

From: Sean Christopherson <[email protected]>

As the first step to create TDX guest, create/destroy VM struct. Assign TDX private Host Key ID (HKID) to the TDX guest for memory encryption and allocate extra pages for the TDX guest. On destruction, free allocated pages, and HKID.

Before tearing down private page tables, TDX requires some resources of the guest TD to be destroyed (i.e. HKID must have been reclaimed, etc). Add flush_shadow_all_private callback before tearing down private page tables for it.

Add vm_free() of kvm_x86_ops hook at the end of kvm_arch_destroy_vm() because some per-VM TDX resources, e.g. TDR, need to be freed after other TDX resources, e.g. HKID, were freed.

---
Changes v10 -> v11:
- Fix doule free in tdx_vm_free() by setting NULL.
- replace struct tdx_td_page tdr and tdcs from struct kvm_tdx with unsigned long

---
 arch/x86/include/asm/kvm-x86-ops.h |   2 +
 arch/x86/include/asm/kvm_host.h    |   2 +
 arch/x86/kvm/vmx/main.c            |  34 ++-
 arch/x86/kvm/vmx/tdx.c             | 415 +++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/tdx.h             |   6 +-
 arch/x86/kvm/vmx/x86_ops.h         |   9 +
 arch/x86/kvm/x86.c                 |   8 +
 7 files changed, 472 insertions(+), 4 deletions(-)
(quoted diff) +0 -0

b/arch/x86/include/asm/kvm-x86-ops.h index e6708bb3f4f6..552de893af75 100644 --- a/arch/x86/include/asm/kvm-x86-ops.h
+++ b/arch/x86/include/asm/kvm-x86-ops.h

arch/x86/include/asm/kvm-x86-ops.h +2 -0
@@ -22,7 +22,9 @@ 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(flush_shadow_all_private)
 KVM_X86_OP_OPTIONAL(vm_destroy)
+KVM_X86_OP_OPTIONAL(vm_free)
 KVM_X86_OP_OPTIONAL_RET0(vcpu_precreate)
 KVM_X86_OP(vcpu_create)
 KVM_X86_OP(vcpu_free)
(quoted diff) +0 -0

b/arch/x86/include/asm/kvm_host.h index 234c28c8e6ee..e199ddf0bb00 100644

arch/x86/include/asm/kvm_host.h +2 -0
@@ -1540,7 +1540,9 @@ struct kvm_x86_ops {
 	bool (*is_vm_type_supported)(unsigned long vm_type);
 	unsigned int vm_size;
 	int (*vm_init)(struct kvm *kvm);
+	void (*flush_shadow_all_private)(struct kvm *kvm);
 	void (*vm_destroy)(struct kvm *kvm);
+	void (*vm_free)(struct kvm *kvm);
 
 	/* Create, but do not attach this VCPU */
 	int (*vcpu_precreate)(struct kvm *kvm);
arch/x86/kvm/vmx/main.c +6 -1
index 781fbc896120..c5f2515026e9 100644
@@ -29,14 +29,40 @@ static __init int vt_hardware_setup(void)
 	return 0;
 }
 
+static void vt_hardware_unsetup(void)
+{
+	tdx_hardware_unsetup();
+	vmx_hardware_unsetup();
+}
+
 static int vt_vm_init(struct kvm *kvm)
 {
 	if (is_td(kvm))
-		return -EOPNOTSUPP;	/* Not ready to create guest

TD yet. */

arch/x86/kvm/vmx/main.c +25 -2
+		return tdx_vm_init(kvm);
 
 	return vmx_vm_init(kvm);
 }
 
+static void vt_flush_shadow_all_private(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		tdx_mmu_release_hkid(kvm);
+}
+
+static void vt_vm_destroy(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		return;
+
+	vmx_vm_destroy(kvm);
+}
+
+static void vt_vm_free(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		tdx_vm_free(kvm);
+}
+
 static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 {
 	if (!is_td(kvm))
@@ -50,7 +76,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.check_processor_compatibility = vmx_check_processor_compat,
 
-	.hardware_unsetup = vmx_hardware_unsetup,
+	.hardware_unsetup = vt_hardware_unsetup,
 
 	.hardware_enable = vmx_hardware_enable,
 	.hardware_disable = vmx_hardware_disable,
@@ -59,7 +85,9 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.is_vm_type_supported = vt_is_vm_type_supported,
 	.vm_size = sizeof(struct kvm_vmx),
 	.vm_init = vt_vm_init,
-	.vm_destroy = vmx_vm_destroy,
+	.flush_shadow_all_private = vt_flush_shadow_all_private,
+	.vm_destroy = vt_vm_destroy,
+	.vm_free = vt_vm_free,
 
 	.vcpu_precreate = vmx_vcpu_precreate,
 	.vcpu_create = vmx_vcpu_create,
arch/x86/kvm/vmx/tdx.c +4 -0
index 2bd1cc37abab..d11950d18226 100644
@@ -6,6 +6,7 @@
 #include "capabilities.h"
 #include "x86_ops.h"
 #include "tdx.h"
+#include "tdx_ops.h"
 #include "x86.h"
 
 #undef pr_fmt
@@ -32,6 +33,250 @@ struct tdx_capabilities {
 /* Capabilities of KVM + the TDX module. */
 static struct tdx_capabilities tdx_caps;
 
+/*
+ * Some TDX SEAMCALLs (TDH.MNG.CREATE, TDH.PHYMEM.CACHE.WB,
+ * TDH.MNG.KEY.RECLAIMID, TDH.MNG.KEY.FREEID etc) tries to acquire a

global lock
+ * internally in TDX module. If failed, TDX_OPERAND_BUSY is returned without
+ * spinning or waiting due to a constraint on execution time. It's caller's
+ * responsibility to avoid race (or retry on TDX_OPERAND_BUSY). Use this mutex
+ * to avoid race in TDX module because the kernel knows better about scheduling.
+ */

arch/x86/kvm/vmx/tdx.c +27 -0
+static DEFINE_MUTEX(tdx_lock);
+static struct mutex *tdx_mng_key_config_lock;
+
+static __always_inline hpa_t set_hkid_to_hpa(hpa_t pa, u16 hkid)
+{
+	return pa | ((hpa_t)hkid << boot_cpu_data.x86_phys_bits);
+}
+
+static inline bool is_td_created(struct kvm_tdx *kvm_tdx)
+{
+	return kvm_tdx->tdr_pa;
+}
+
+static inline void tdx_hkid_free(struct kvm_tdx *kvm_tdx)
+{
+	tdx_keyid_free(kvm_tdx->hkid);
+	kvm_tdx->hkid = 0;
+}
+
+static inline bool is_hkid_assigned(struct kvm_tdx *kvm_tdx)
+{
+	return kvm_tdx->hkid > 0;
+}
+
+static void tdx_clear_page(unsigned long page_pa)
+{
+	const void *zero_page = (const void *)

__va(page_to_phys(ZERO_PAGE(0)));

arch/x86/kvm/vmx/tdx.c +11 -0
+	void *page = __va(page_pa);
+	unsigned long i;
+
+	if (!static_cpu_has(X86_FEATURE_MOVDIR64B)) {
+		clear_page(page);
+		return;
+	}
+
+	/*
+	 * Zeroing the page is only necessary for systems with MKTME-i:
+	 * when re-assign one page from old keyid to a new keyid,

MOVDIR64B is

arch/x86/kvm/vmx/tdx.c +1 -0
+	 * required to clear/write the page with new keyid to prevent

integrity

arch/x86/kvm/vmx/tdx.c +4 -0
+	 * error when read on the page with new keyid.
+	 *
+	 * clflush doesn't flush cache with HKID set.
+	 * The cache line could be poisoned (even without MKTME-i),

clear the

arch/x86/kvm/vmx/tdx.c +6 -0
+	 * poison bit.
+	 */
+	for (i = 0; i < PAGE_SIZE; i += 64)
+		movdir64b(page + i, zero_page);
+	/*
+	 * MOVDIR64B store uses WC buffer.  Prevent following memory

reads

arch/x86/kvm/vmx/tdx.c +14 -0
+	 * from seeing potentially poisoned cache.
+	 */
+	__mb();
+}
+
+static int tdx_reclaim_page(hpa_t pa, bool do_wb, u16 hkid)
+{
+	struct tdx_module_output out;
+	u64 err;
+
+	do {
+		err = tdh_phymem_page_reclaim(pa, &out);
+		/*
+		 * TDH.PHYMEM.PAGE.RECLAIM is allowed only when TD is

shutdown.

arch/x86/kvm/vmx/tdx.c +2 -0
+		 * state.  i.e. destructing TD.
+		 * TDH.PHYMEM.PAGE.RECLAIM  requires TDR and target

page.

arch/x86/kvm/vmx/tdx.c +1 -0
+		 * Because we're destructing TD, it's rare to contend

with TDR.

arch/x86/kvm/vmx/tdx.c +10 -0
+		 */
+	} while (err == (TDX_OPERAND_BUSY | TDX_OPERAND_ID_RCX));
+	if (WARN_ON_ONCE(err)) {
+		pr_tdx_error(TDH_PHYMEM_PAGE_RECLAIM, err, &out);
+		return -EIO;
+	}
+
+	if (do_wb) {
+		/*
+		 * Only TDR page gets into this path.  No contention is

expected

arch/x86/kvm/vmx/tdx.c +15 -0
+		 * because of the last page of TD.
+		 */
+		err = tdh_phymem_page_wbinvd(set_hkid_to_hpa(pa, hkid));
+		if (WARN_ON_ONCE(err)) {
+			pr_tdx_error(TDH_PHYMEM_PAGE_WBINVD, err, NULL);
+			return -EIO;
+		}
+	}
+
+	tdx_clear_page(pa);
+	return 0;
+}
+
+static void tdx_reclaim_td_page(unsigned long td_page_pa)
+{

arch/x86/kvm/vmx/tdx.c struct tdx_capabilities { · line 144

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	if (!td_page_pa)
+		return;
+	/*
+	 * TDCX are being reclaimed.  TDX module maps TDCX with HKID
+	 * assigned to the TD.  Here the cache associated to the TD
+	 * was already flushed by TDH.PHYMEM.CACHE.WB before here, So
+	 * cache doesn't need to be flushed again.
+	 */
+	if (WARN_ON(tdx_reclaim_page(td_page_pa, false, 0)))
+		/* If reclaim failed, leak the page. */

Better add a FIXME: here as this has to be fixed later.

Quoted 435 lines
arch/x86/kvm/vmx/tdx.c +43 -0
+		return;
+	free_page((unsigned long)__va(td_page_pa));
+}
+
+static int tdx_do_tdh_phymem_cache_wb(void *param)
+{
+	u64 err = 0;
+
+	do {
+		err = tdh_phymem_cache_wb(!!err);
+	} while (err == TDX_INTERRUPTED_RESUMABLE);
+
+	/* Other thread may have done for us. */
+	if (err == TDX_NO_HKID_READY_TO_WBCACHE)
+		err = TDX_SUCCESS;
+	if (WARN_ON_ONCE(err)) {
+		pr_tdx_error(TDH_PHYMEM_CACHE_WB, err, NULL);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+void tdx_mmu_release_hkid(struct kvm *kvm)
+{
+	struct kvm_tdx *kvm_tdx = to_kvm_tdx(kvm);
+	cpumask_var_t packages;
+	bool cpumask_allocated;
+	u64 err;
+	int ret;
+	int i;
+
+	if (!is_hkid_assigned(kvm_tdx))
+		return;
+
+	if (!is_td_created(kvm_tdx))
+		goto free_hkid;
+
+	cpumask_allocated = zalloc_cpumask_var(&packages, GFP_KERNEL);
+	cpus_read_lock();
+	for_each_online_cpu(i) {
+		if (cpumask_allocated &&
+

cpumask_test_and_set_cpu(topology_physical_package_id(i),

arch/x86/kvm/vmx/tdx.c +6 -0
+						packages))
+			continue;
+
+		/*
+		 * We can destroy multiple the guest TDs simultaneously.
+		 * Prevent tdh_phymem_cache_wb from returning TDX_BUSY

by

arch/x86/kvm/vmx/tdx.c +4 -0
+		 * serialization.
+		 */
+		mutex_lock(&tdx_lock);
+		ret = smp_call_on_cpu(i, tdx_do_tdh_phymem_cache_wb,

NULL, 1);

arch/x86/kvm/vmx/tdx.c +13 -0
+		mutex_unlock(&tdx_lock);
+		if (ret)
+			break;
+	}
+	cpus_read_unlock();
+	free_cpumask_var(packages);
+
+	mutex_lock(&tdx_lock);
+	err = tdh_mng_key_freeid(kvm_tdx->tdr_pa);
+	mutex_unlock(&tdx_lock);
+	if (WARN_ON_ONCE(err)) {
+		pr_tdx_error(TDH_MNG_KEY_FREEID, err, NULL);
+		pr_err("tdh_mng_key_freeid failed. HKID %d is

leaked.\n",

arch/x86/kvm/vmx/tdx.c +28 -0
+			kvm_tdx->hkid);
+		return;
+	}
+
+free_hkid:
+	tdx_hkid_free(kvm_tdx);
+}
+
+void tdx_vm_free(struct kvm *kvm)
+{
+	struct kvm_tdx *kvm_tdx = to_kvm_tdx(kvm);
+	int i;
+
+	/* Can't reclaim or free TD pages if teardown failed. */
+	if (is_hkid_assigned(kvm_tdx))
+		return;
+
+	if (kvm_tdx->tdcs_pa) {
+		for (i = 0; i < tdx_caps.tdcs_nr_pages; i++)
+			tdx_reclaim_td_page(kvm_tdx->tdcs_pa[i]);
+		kfree(kvm_tdx->tdcs_pa);
+		kvm_tdx->tdcs_pa = NULL;
+	}
+
+	if (!kvm_tdx->tdr_pa)
+		return;
+	/*
+	 * TDX module maps TDR with TDX global HKID.  TDX module may

access TDR

arch/x86/kvm/vmx/tdx.c +1 -0
+	 * while operating on TD (Especially reclaiming TDCS).  Cache

flush with

arch/x86/kvm/vmx/tdx.c +19 -0
+	 * TDX global HKID is needed.
+	 */
+	if (tdx_reclaim_page(kvm_tdx->tdr_pa, true, tdx_global_keyid))
+		return;
+
+	free_page((unsigned long)__va(kvm_tdx->tdr_pa));
+	kvm_tdx->tdr_pa = 0;
+}
+
+static int tdx_do_tdh_mng_key_config(void *param)
+{
+	hpa_t *tdr_p = param;
+	u64 err;
+
+	do {
+		err = tdh_mng_key_config(*tdr_p);
+
+		/*
+		 * If it failed to generate a random key, retry it

because this

arch/x86/kvm/vmx/tdx.c +1 -0
+		 * is typically caused by an entropy error of the CPU's

random

arch/x86/kvm/vmx/tdx.c +68 -0
+		 * number generator.
+		 */
+	} while (err == TDX_KEY_GENERATION_FAILED);
+
+	if (WARN_ON_ONCE(err)) {
+		pr_tdx_error(TDH_MNG_KEY_CONFIG, err, NULL);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int __tdx_td_init(struct kvm *kvm);
+
+int tdx_vm_init(struct kvm *kvm)
+{
+	/* Place holder for now. */
+	return __tdx_td_init(kvm);
+}
+
 int tdx_dev_ioctl(void __user *argp)
 {
 	struct kvm_tdx_capabilities __user *user_caps;
@@ -78,6 +323,160 @@ int tdx_dev_ioctl(void __user *argp)
 	return 0;
 }
 
+static int __tdx_td_init(struct kvm *kvm)
+{
+	struct kvm_tdx *kvm_tdx = to_kvm_tdx(kvm);
+	cpumask_var_t packages;
+	unsigned long *tdcs_pa = NULL;
+	unsigned long tdr_pa = 0;
+	unsigned long va;
+	int ret, i;
+	u64 err;
+
+	ret = tdx_keyid_alloc();
+	if (ret < 0)
+		return ret;
+	kvm_tdx->hkid = ret;
+
+	va = __get_free_page(GFP_KERNEL_ACCOUNT);
+	if (!va)
+		goto free_hkid;
+	tdr_pa = __pa(va);
+
+	tdcs_pa = kcalloc(tdx_caps.tdcs_nr_pages,
 sizeof(*kvm_tdx->tdcs_pa),
+			  GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+	if (!tdcs_pa)
+		goto free_tdr;
+	for (i = 0; i < tdx_caps.tdcs_nr_pages; i++) {
+		va = __get_free_page(GFP_KERNEL_ACCOUNT);
+		if (!va)
+			goto free_tdcs;
+		tdcs_pa[i] = __pa(va);
+	}
+
+	if (!zalloc_cpumask_var(&packages, GFP_KERNEL)) {
+		ret = -ENOMEM;
+		goto free_tdcs;
+	}
+	cpus_read_lock();
+	/*
+	 * Need at least one CPU of the package to be online in order to
+	 * program all packages for host key id.  Check it.
+	 */
+	for_each_present_cpu(i)
+		cpumask_set_cpu(topology_physical_package_id(i),
 packages);
+	for_each_online_cpu(i)
+		cpumask_clear_cpu(topology_physical_package_id(i),
 packages);
+	if (!cpumask_empty(packages)) {
+		ret = -EIO;
+		/*
+		 * Because it's hard for human operator to figure out

the

arch/x86/kvm/vmx/tdx.c +3 -0
+		 * reason, warn it.
+		 */
+		pr_warn("All packages need to have online CPU to create

TD. Online CPU and retry.\n");

arch/x86/kvm/vmx/tdx.c +6 -0
+		goto free_packages;
+	}
+
+	/*
+	 * Acquire global lock to avoid TDX_OPERAND_BUSY:
+	 * TDH.MNG.CREATE and other APIs try to lock the global Key

Owner

arch/x86/kvm/vmx/tdx.c +1 -0
+	 * Table (KOT) to track the assigned TDX private HKID.  It

doesn't spin

arch/x86/kvm/vmx/tdx.c +1 -0
+	 * to acquire the lock, returns TDX_OPERAND_BUSY instead, and

let the

arch/x86/kvm/vmx/tdx.c +1 -0
+	 * caller to handle the contention.  This is because of time

limitation

arch/x86/kvm/vmx/tdx.c +1 -0
+	 * usable inside the TDX module and OS/VMM knows better about

process

arch/x86/kvm/vmx/tdx.c +25 -0
+	 * scheduling.
+	 *
+	 * APIs to acquire the lock of KOT:
+	 * TDH.MNG.CREATE, TDH.MNG.KEY.FREEID, TDH.MNG.VPFLUSHDONE, and
+	 * TDH.PHYMEM.CACHE.WB.
+	 */
+	mutex_lock(&tdx_lock);
+	err = tdh_mng_create(tdr_pa, kvm_tdx->hkid);
+	mutex_unlock(&tdx_lock);
+	if (WARN_ON_ONCE(err)) {
+		pr_tdx_error(TDH_MNG_CREATE, err, NULL);
+		ret = -EIO;
+		goto free_packages;
+	}
+	kvm_tdx->tdr_pa = tdr_pa;
+
+	for_each_online_cpu(i) {
+		int pkg = topology_physical_package_id(i);
+
+		if (cpumask_test_and_set_cpu(pkg, packages))
+			continue;
+
+		/*
+		 * Program the memory controller in the package with an
+		 * encryption key associated to a TDX private host key

id

arch/x86/kvm/vmx/tdx.c +1 -0
+		 * assigned to this TDR.  Concurrent operations on same

memory

arch/x86/kvm/vmx/tdx.c +1 -0
+		 * controller results in TDX_OPERAND_BUSY.  Avoid this

race by

arch/x86/kvm/vmx/tdx.c +21 -0
+		 * mutex.
+		 */
+		mutex_lock(&tdx_mng_key_config_lock[pkg]);
+		ret = smp_call_on_cpu(i, tdx_do_tdh_mng_key_config,
+				      &kvm_tdx->tdr_pa, true);
+		mutex_unlock(&tdx_mng_key_config_lock[pkg]);
+		if (ret)
+			break;
+	}
+	cpus_read_unlock();
+	free_cpumask_var(packages);
+	if (ret)
+		goto teardown;
+
+	kvm_tdx->tdcs_pa = tdcs_pa;
+	for (i = 0; i < tdx_caps.tdcs_nr_pages; i++) {
+		err = tdh_mng_addcx(kvm_tdx->tdr_pa, tdcs_pa[i]);
+		if (WARN_ON_ONCE(err)) {
+			pr_tdx_error(TDH_MNG_ADDCX, err, NULL);
+			for (i++; i < tdx_caps.tdcs_nr_pages; i++) {
+				free_page((unsigned

long)__va(tdcs_pa[i]));

arch/x86/kvm/vmx/tdx.c +9 -0
+				tdcs_pa[i] = 0;
+			}
+			ret = -EIO;
+			goto teardown;
+		}
+	}
+
+	/*
+	 * Note, TDH_MNG_INIT cannot be invoked here.  TDH_MNG_INIT

requires a dedicated

arch/x86/kvm/vmx/tdx.c +6 -0
+	 * ioctl() to define the configure CPUID values for the TD.
+	 */
+	return 0;
+
+	/*
+	 * The sequence for freeing resources from a partially

initialized TD

arch/x86/kvm/vmx/tdx.c +1 -0
+	 * varies based on where in the initialization flow failure

occurred.

arch/x86/kvm/vmx/tdx.c +1 -0
+	 * Simply use the full teardown and destroy, which naturally

play nice

arch/x86/kvm/vmx/tdx.c +38 -0
+	 * with partial initialization.
+	 */
+teardown:
+	tdx_mmu_release_hkid(kvm);
+	tdx_vm_free(kvm);
+	return ret;
+
+free_packages:
+	cpus_read_unlock();
+	free_cpumask_var(packages);
+free_tdcs:
+	for (i = 0; i < tdx_caps.tdcs_nr_pages; i++) {
+		if (tdcs_pa[i])
+			free_page((unsigned long)__va(tdcs_pa[i]));
+	}
+	kfree(tdcs_pa);
+	kvm_tdx->tdcs_pa = NULL;
+
+free_tdr:
+	if (tdr_pa)
+		free_page((unsigned long)__va(tdr_pa));
+	kvm_tdx->tdr_pa = 0;
+free_hkid:
+	if (is_hkid_assigned(kvm_tdx))
+		tdx_hkid_free(kvm_tdx);
+	return ret;
+}
+
 int tdx_vm_ioctl(struct kvm *kvm, void __user *argp)
 {
 	struct kvm_tdx_cmd tdx_cmd;
@@ -152,6 +551,8 @@ bool tdx_is_vm_type_supported(unsigned long type)
 
 int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops)
 {
+	int max_pkgs;
+	int i;
 	int r;
 
 	if (!enable_ept) {
@@ -159,6 +560,14 @@ int __init tdx_hardware_setup(struct kvm_x86_ops
 *x86_ops) return -EINVAL;
 	}
 
+	max_pkgs = topology_max_packages();
+	tdx_mng_key_config_lock = kcalloc(max_pkgs,
 sizeof(*tdx_mng_key_config_lock),
+				   GFP_KERNEL);
+	if (!tdx_mng_key_config_lock)
+		return -ENOMEM;
+	for (i = 0; i < max_pkgs; i++)
+		mutex_init(&tdx_mng_key_config_lock[i]);
+
 	/* TDX requires VMX. */
 	r = vmxon_all();
arch/x86/kvm/vmx/tdx.c +6 -0
 	if (!r)
@@ -167,3 +576,9 @@ int __init tdx_hardware_setup(struct kvm_x86_ops
 *x86_ops) 
 	return r;
 }
+
+void tdx_hardware_unsetup(void)
+{
+	/* kfree accepts NULL. */
+	kfree(tdx_mng_key_config_lock);
+}
arch/x86/kvm/vmx/tdx.h +5 -1
index 473013265bd8..e78d72cf4c3a 100644
@@ -5,7 +5,11 @@
 #ifdef CONFIG_INTEL_TDX_HOST
 struct kvm_tdx {
 	struct kvm kvm;
-	/* TDX specific members follow. */
+
+	unsigned long tdr_pa;
+	unsigned long *tdcs_pa;
+
+	int hkid;
 };
 
 struct vcpu_tdx {
arch/x86/kvm/vmx/x86_ops.h +8 -0
index bb4090dbae37..3d0f519727c6 100644
@@ -139,15 +139,24 @@ void vmx_setup_mce(struct kvm_vcpu *vcpu);
 
 #ifdef CONFIG_INTEL_TDX_HOST
 int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops);
+void tdx_hardware_unsetup(void);
 bool tdx_is_vm_type_supported(unsigned long type);
 int tdx_dev_ioctl(void __user *argp);
 
+int tdx_vm_init(struct kvm *kvm);
+void tdx_mmu_release_hkid(struct kvm *kvm);
+void tdx_vm_free(struct kvm *kvm);
 int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) {
 return 0; } +static inline void tdx_hardware_unsetup(void) {}
 static inline bool tdx_is_vm_type_supported(unsigned long type) {
 return false; } static inline int tdx_dev_ioctl(void __user *argp) {
 return -EOPNOTSUPP; }; 
+static inline int tdx_vm_init(struct kvm *kvm) { return -EOPNOTSUPP; }
+static inline void tdx_mmu_release_hkid(struct kvm *kvm) {}
+static inline void tdx_flush_shadow_all_private(struct kvm *kvm) {}
+static inline void tdx_vm_free(struct kvm *kvm) {}
 static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) {

return -EOPNOTSUPP; } #endif

arch/x86/kvm/x86.c +3 -0
index 1152a9dc6d84..0fa91a9708aa 100644
@@ -12337,6 +12337,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
 	kvm_page_track_cleanup(kvm);
 	kvm_xen_destroy_vm(kvm);
 	kvm_hv_destroy_vm(kvm);
+	static_call_cond(kvm_x86_vm_free)(kvm);
 }
 
 static void memslot_rmap_free(struct kvm_memory_slot *slot)
@@ -12647,6 +12648,13 @@ void kvm_arch_commit_memory_region(struct kvm
 *kvm, 
 void kvm_arch_flush_shadow_all(struct kvm *kvm)
 {
+	/*
+	 * kvm_mmu_zap_all() zaps both private and shared page tables.

Before

arch/x86/kvm/x86.c +1 -0
+	 * tearing down private page tables, TDX requires some TD

resources to

arch/x86/kvm/x86.c +1 -0
+	 * be destroyed (i.e. keyID must have been reclaimed, etc).

Invoke

arch/x86/kvm/x86.c +3 -0
+	 * kvm_x86_flush_shadow_all_private() for this.
+	 */
+	static_call_cond(kvm_x86_flush_shadow_all_private)(kvm);
 	kvm_mmu_zap_all(kvm);
 }
#39 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 12 lines · Zhi Wang

arch/x86/kvm/vmx/tdx.c struct tdx_capabilities { · line 143

Quoted 11 lines · On Thu, 12 Jan 2023 08:31:26 -0800 [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +11 -0
+static void tdx_reclaim_td_page(unsigned long td_page_pa)
+{
+	if (!td_page_pa)
+		return;
+	/*
+	 * TDCX are being reclaimed.  TDX module maps TDCX with HKID
+	 * assigned to the TD.  Here the cache associated to the TD
+	 * was already flushed by TDH.PHYMEM.CACHE.WB before here, So
+	 * cache doesn't need to be flushed again.
+	 */
+	if (WARN_ON(tdx_reclaim_page(td_page_pa, false, 0)))

The WARN_ON() can go, tdx_reclaim_page() has WARN_ON_ONCE() + pr_tdx_error() in all error paths.

Quoted 3 lines

arch/x86/kvm/vmx/tdx.c struct tdx_capabilities { · line 144

Quoted 1 line
arch/x86/kvm/vmx/tdx.c +1 -0
+		/* If reclaim failed, leak the page. */

Better add a FIXME: here as this has to be fixed later.

No, leaking the page is all KVM can reasonably do here. An improved comment would be helpful, but no code change is required. tdx_reclaim_page() returns an error if and only if there's an unexpected, fatal error, e.g. a SEAMCALL with bad params, incorrect concurrency in KVM, a TDX Module bug, etc. Retrying at a later point is highly unlikely to be successful.

#40 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 22 lines · On Fri, 13 Jan 2023 15:16:08 +0000 Sean Christopherson <[email protected]>…
Quoted 13 lines · Zhi Wang

arch/x86/kvm/vmx/tdx.c struct tdx_capabilities { · line 143

Quoted 12 lines · On Thu, 12 Jan 2023 08:31:26 -0800 [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +8 -0
+static void tdx_reclaim_td_page(unsigned long td_page_pa)
+{
+	if (!td_page_pa)
+		return;
+	/*
+	 * TDCX are being reclaimed.  TDX module maps TDCX with HKID
+	 * assigned to the TD.  Here the cache associated to the TD
+	 * was already flushed by TDH.PHYMEM.CACHE.WB before here,

So

arch/x86/kvm/vmx/tdx.c +3 -0
+	 * cache doesn't need to be flushed again.
+	 */
+	if (WARN_ON(tdx_reclaim_page(td_page_pa, false, 0)))

The WARN_ON() can go, tdx_reclaim_page() has WARN_ON_ONCE() + pr_tdx_error() in all error paths.

Quoted 3 lines

arch/x86/kvm/vmx/tdx.c struct tdx_capabilities { · line 144

Quoted 1 line
arch/x86/kvm/vmx/tdx.c +1 -0
+		/* If reclaim failed, leak the page. */

Better add a FIXME: here as this has to be fixed later.

Quoted 6 lines

No, leaking the page is all KVM can reasonably do here. An improved comment would be helpful, but no code change is required. tdx_reclaim_page() returns an error if and only if there's an unexpected, fatal error, e.g. a SEAMCALL with bad params, incorrect concurrency in KVM, a TDX Module bug, etc. Retrying at a later point is highly unlikely to be successful.

Hi:

The word "leaking" sounds like a situation left unhandled temporarily.

I checked the source code of the TDX module[1] for the possible reason to fail when reviewing this patch:

tdx-module-v1.0.01.01.zip\src\vmm_dispatcher\api_calls\tdh_phymem_page_reclaim.c tdx-module-v1.0.01.01.zip\src\vmm_dispatcher\api_calls\tdh_phymem_page_wbinvd.c

a. Invalid parameters. For example, page is not aligned, PA HKID is not zero...

For invalid parameters, a WARN_ON_ONCE() + return value is good enough as that is how kernel handles similar situations. The caller takes the responsibility.

b. Locks has been taken in TDX module. TDR page has been locked due to another SEAMCALL, another SEAMCALL is doing PAMT walk and holding PAMT lock...

This needs to be improved later either by retry or taking tdx_lock to avoid TDX module fails on this.

[1] https://www.intel.com/content/www/us/en/download/738875/738876/intel-trust-domain-extension-intel-tdx-module.html

#41 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 28 lines · Zhi Wang
Quoted 9 lines · On Fri, 13 Jan 2023 15:16:08 +0000 > Sean Christopherson <[email protected]>…
Quoted 1 line · Zhi Wang

Better add a FIXME: here as this has to be fixed later.

No, leaking the page is all KVM can reasonably do here. An improved comment would be helpful, but no code change is required. tdx_reclaim_page() returns an error if and only if there's an unexpected, fatal error, e.g. a SEAMCALL with bad params, incorrect concurrency in KVM, a TDX Module bug, etc. Retrying at a later point is highly unlikely to be successful.

Hi:

The word "leaking" sounds like a situation left unhandled temporarily.

I checked the source code of the TDX module[1] for the possible reason to fail when reviewing this patch:

tdx-module-v1.0.01.01.zip\src\vmm_dispatcher\api_calls\tdh_phymem_page_reclaim.c tdx-module-v1.0.01.01.zip\src\vmm_dispatcher\api_calls\tdh_phymem_page_wbinvd.c

a. Invalid parameters. For example, page is not aligned, PA HKID is not zero...

For invalid parameters, a WARN_ON_ONCE() + return value is good enough as that is how kernel handles similar situations. The caller takes the responsibility.

Quoted 5 lines

b. Locks has been taken in TDX module. TDR page has been locked due to another SEAMCALL, another SEAMCALL is doing PAMT walk and holding PAMT lock...

This needs to be improved later either by retry or taking tdx_lock to avoid TDX module fails on this.

No, tdx_reclaim_page() already retries TDH.PHYMEM.PAGE.RECLAIM if the target page is contended (though I'd question the validity of even that), and TDH.PHYMEM.PAGE.WBINVD is performed only when reclaiming the TDR. If there's contention when reclaiming the TDR, then KVM effectively has a use-after-free bug, i.e. leaking the page is the least of our worries.

Quoted 34 lines · On Thu, Jan 12, 2023 at 8:34 AM <[email protected]> wrote:
arch/x86/kvm/vmx/tdx.c +34 -0
+static int tdx_reclaim_page(hpa_t pa, bool do_wb, u16 hkid)
+{
+       struct tdx_module_output out;
+       u64 err;
+
+       do {
+               err = tdh_phymem_page_reclaim(pa, &out);
+               /*
+                * TDH.PHYMEM.PAGE.RECLAIM is allowed only when TD is shutdown.
+                * state.  i.e. destructing TD.
+                * TDH.PHYMEM.PAGE.RECLAIM  requires TDR and target page.
+                * Because we're destructing TD, it's rare to contend with TDR.
+                */
+       } while (err == (TDX_OPERAND_BUSY | TDX_OPERAND_ID_RCX));
+       if (WARN_ON_ONCE(err)) {
+               pr_tdx_error(TDH_PHYMEM_PAGE_RECLAIM, err, &out);
+               return -EIO;
+       }
+
+       if (do_wb) {
+               /*
+                * Only TDR page gets into this path.  No contention is expected
+                * because of the last page of TD.
+                */
+               err = tdh_phymem_page_wbinvd(set_hkid_to_hpa(pa, hkid));
+               if (WARN_ON_ONCE(err)) {
+                       pr_tdx_error(TDH_PHYMEM_PAGE_WBINVD, err, NULL);
+                       return -EIO;
+               }
+       }
+
+       tdx_clear_page(pa);
+       return 0;
+}
#42 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 32 lines · On Tue, 17 Jan 2023 15:55:53 +0000 Sean Christopherson <[email protected]>…
Quoted 31 lines · Zhi Wang
Quoted 9 lines · On Fri, 13 Jan 2023 15:16:08 +0000 > Sean Christopherson <[email protected]>…
Quoted 1 line · Zhi Wang

Better add a FIXME: here as this has to be fixed later.

No, leaking the page is all KVM can reasonably do here. An improved comment would be helpful, but no code change is required. tdx_reclaim_page() returns an error if and only if there's an unexpected, fatal error, e.g. a SEAMCALL with bad params, incorrect concurrency in KVM, a TDX Module bug, etc. Retrying at a later point is highly unlikely to be successful.

Hi:

The word "leaking" sounds like a situation left unhandled temporarily.

I checked the source code of the TDX module[1] for the possible reason to fail when reviewing this patch:

tdx-module-v1.0.01.01.zip\src\vmm_dispatcher\api_calls\tdh_phymem_page_reclaim.c tdx-module-v1.0.01.01.zip\src\vmm_dispatcher\api_calls\tdh_phymem_page_wbinvd.c

a. Invalid parameters. For example, page is not aligned, PA HKID is not zero...

For invalid parameters, a WARN_ON_ONCE() + return value is good enough as that is how kernel handles similar situations. The caller takes the responsibility.

b. Locks has been taken in TDX module. TDR page has been locked due to another SEAMCALL, another SEAMCALL is doing PAMT walk and holding PAMT lock...

Quoted 9 lines
Quoted 2 lines

This needs to be improved later either by retry or taking tdx_lock to avoid TDX module fails on this.

No, tdx_reclaim_page() already retries TDH.PHYMEM.PAGE.RECLAIM if the target page is contended (though I'd question the validity of even that), and TDH.PHYMEM.PAGE.WBINVD is performed only when reclaiming the TDR. If there's contention when reclaiming the TDR, then KVM effectively has a use-after-free bug, i.e. leaking the page is the least of our worries.

Hi:

Thanks for the reply. "Leaking" is the consquence of even failing in retry. I agree with this. But I was questioning if "retry" is really a correct and only solution when encountering lock contention in the TDX module as I saw that there are quite some magic numbers are going to be introduced because of "retry" and there were discussions about times of retry should be 3 or 1000 in TDX guest on hyper-V patches. It doesn't sound right.

Compare to an typical *kernel lock* case, an execution path can wait on a waitqueue and later will be woken up. We usually do contention-wait-and-retry and we rarely just do contention and retry X times. In TDX case, I understand that it is hard for the TDX module to provide similar solutions as an execution path can't stay long in the TDX module.

1) We can always take tdx_lock (linux kernel lock) when calling a SEAMCALL that touch the TDX internal locks. But the downside is we might lose some concurrency.

2) As TDX module doesn't provide contention-and-wait, I guess the following approach might have been discussed when designing this "retry".

KERNEL TDX MODULE

SEAMCALL A -> PATH A: Taking locks

SEAMCALL B -> PATH B: Contention on a lock

<- Return "operand busy"

SEAMCALL B   -|
              |  <- Wait on a kernel waitqueue
SEAMCALL B  <-|

SEAMCALL A <- PATH A: Return

SEAMCALL A   -|
              |  <- Wake up the waitqueue
SEMACALL A  <-| 

SEAMCALL B -> PATH B: Taking the locks
...

Why not this scheme wasn't chosen?

Quoted 36 lines · On Thu, Jan 12, 2023 at 8:34 AM <[email protected]> wrote:
Quoted 34 lines
arch/x86/kvm/vmx/tdx.c +34 -0
+static int tdx_reclaim_page(hpa_t pa, bool do_wb, u16 hkid)
+{
+       struct tdx_module_output out;
+       u64 err;
+
+       do {
+               err = tdh_phymem_page_reclaim(pa, &out);
+               /*
+                * TDH.PHYMEM.PAGE.RECLAIM is allowed only when TD is shutdown.
+                * state.  i.e. destructing TD.
+                * TDH.PHYMEM.PAGE.RECLAIM  requires TDR and target page.
+                * Because we're destructing TD, it's rare to contend with TDR.
+                */
+       } while (err == (TDX_OPERAND_BUSY | TDX_OPERAND_ID_RCX));
+       if (WARN_ON_ONCE(err)) {
+               pr_tdx_error(TDH_PHYMEM_PAGE_RECLAIM, err, &out);
+               return -EIO;
+       }
+
+       if (do_wb) {
+               /*
+                * Only TDR page gets into this path.  No contention is expected
+                * because of the last page of TD.
+                */
+               err = tdh_phymem_page_wbinvd(set_hkid_to_hpa(pa, hkid));
+               if (WARN_ON_ONCE(err)) {
+                       pr_tdx_error(TDH_PHYMEM_PAGE_WBINVD, err, NULL);
+                       return -EIO;
+               }
+       }
+
+       tdx_clear_page(pa);
+       return 0;
+}
#43 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 45 lines · Zhi Wang
Quoted 32 lines · On Tue, 17 Jan 2023 15:55:53 +0000 Sean Christopherson <[email protected]>…
Quoted 31 lines · Zhi Wang
Quoted 9 lines · On Fri, 13 Jan 2023 15:16:08 +0000 > Sean Christopherson <[email protected]>…
Quoted 1 line · Zhi Wang

Better add a FIXME: here as this has to be fixed later.

No, leaking the page is all KVM can reasonably do here. An improved comment would be helpful, but no code change is required. tdx_reclaim_page() returns an error if and only if there's an unexpected, fatal error, e.g. a SEAMCALL with bad params, incorrect concurrency in KVM, a TDX Module bug, etc. Retrying at a later point is highly unlikely to be successful.

Hi:

The word "leaking" sounds like a situation left unhandled temporarily.

I checked the source code of the TDX module[1] for the possible reason to fail when reviewing this patch:

tdx-module-v1.0.01.01.zip\src\vmm_dispatcher\api_calls\tdh_phymem_page_reclaim.c tdx-module-v1.0.01.01.zip\src\vmm_dispatcher\api_calls\tdh_phymem_page_wbinvd.c

a. Invalid parameters. For example, page is not aligned, PA HKID is not zero...

For invalid parameters, a WARN_ON_ONCE() + return value is good enough as that is how kernel handles similar situations. The caller takes the responsibility.

b. Locks has been taken in TDX module. TDR page has been locked due to another SEAMCALL, another SEAMCALL is doing PAMT walk and holding PAMT lock...

Quoted 9 lines
Quoted 2 lines

This needs to be improved later either by retry or taking tdx_lock to avoid TDX module fails on this.

No, tdx_reclaim_page() already retries TDH.PHYMEM.PAGE.RECLAIM if the target page is contended (though I'd question the validity of even that), and TDH.PHYMEM.PAGE.WBINVD is performed only when reclaiming the TDR. If there's contention when reclaiming the TDR, then KVM effectively has a use-after-free bug, i.e. leaking the page is the least of our worries.

Quoted 8 lines

Hi:

Thanks for the reply. "Leaking" is the consquence of even failing in retry. I agree with this. But I was questioning if "retry" is really a correct and only solution when encountering lock contention in the TDX module as I saw that there are quite some magic numbers are going to be introduced because of "retry" and there were discussions about times of retry should be 3 or 1000 in TDX guest on hyper-V patches. It doesn't sound right.

Ah, yeah, I'm speaking only with respect to leaking pages on failure in this specific scenario.

Quoted 5 lines

Compare to an typical *kernel lock* case, an execution path can wait on a waitqueue and later will be woken up. We usually do contention-wait-and-retry and we rarely just do contention and retry X times. In TDX case, I understand that it is hard for the TDX module to provide similar solutions as an execution path can't stay long in the TDX module.

Let me preface the below comments by saying that this is the first time that I've seen the "Single-Step and Zero-Step Attacks Mitigation Mechanisms" behavior, i.e. the first time I've been made aware that the TDX Module can apparently decide to take S-EPT locks in the VM-Enter path.

Quoted 4 lines

1) We can always take tdx_lock (linux kernel lock) when calling a SEAMCALL that touch the TDX internal locks. But the downside is we might lose some concurrency.

This isn't really feasible in practice. Even if tdx_lock were made a spinlock (it's currently a mutex) so that it could it could be taken inside kvm->mmu_lock, acquiring a per-VM lock, let alone a global lock, in KVM's page fault handling path is not an option. KVM has a hard requirement these days of being able to handle multiple page faults in parallel.

Quoted 17 lines

2) As TDX module doesn't provide contention-and-wait, I guess the following approach might have been discussed when designing this "retry".

KERNEL TDX MODULE

SEAMCALL A -> PATH A: Taking locks

SEAMCALL B -> PATH B: Contention on a lock

<- Return "operand busy"

SEAMCALL B   -|
              |  <- Wait on a kernel waitqueue
SEAMCALL B  <-|

SEAMCALL A <- PATH A: Return

Quoted 8 lines
SEAMCALL A   -|
              |  <- Wake up the waitqueue
SEMACALL A  <-| 

SEAMCALL B -> PATH B: Taking the locks
...

Why not this scheme wasn't chosen?

AFAIK, I don't think a waitqueue approach as ever been discussed publicly. Intel may have considered the idea internally, but I don't recall anything being proposed publically (though it's entirely possible I just missed the discussion).

Anways, I don't think a waitqueue would be a good fit, at least not for S-EPT management, which AFAICT is the only scenario where KVM does the arbitrary "retry X times and hope things work". If the contention occurs due to the TDX Module taking an S-EPT lock in VM-Enter, then KVM won't get a chance to do the "Wake up the waitqueue" action until the next VM-Exit, which IIUC is well after the TDX Module drops the S-EPT lock. In other words, immediately retrying and then punting the problem further up the stack in KVM does seem to be the least awful "solution" if there's contention.

That said, I 100% agree that the arbitrary retry stuff is awful. The zero-step interaction in particular isn't acceptable.

Intel folks, encountering "TDX_OPERAND_BUSY | TDX_OPERAND_ID_SEPT" on VM-Enter needs to be treated as a KVM bug, even if it means teaching KVM to kill the VM if a vCPU is on the cusp of triggerring the "pre-determined number" of EPT faults mentioned in this snippet:

After a pre-determined number of such EPT violations occur on the same instruction, the TDX module starts tracking the GPAs that caused Secure EPT faults and fails further host VMM attempts to enter the TD VCPU unless previously faulting private GPAs are properly mapped in the Secure EPT.

If the "pre-determined number" is too low to avoid false positives, e.g. if it can be tripped by a vCPU spinning while a different vCPU finishes handling a fault, then either the behavior of the TDX Module needs to be revisited, or KVM needs to stall vCPUs that are approaching the threshold until all outstanding S-EPT faults have been serviced.

KVM shouldn't be spuriously zapping private S-EPT entries since the backing memory is pinned, which means the only way for a vCPU to truly get stuck faulting on a single instruction is if userspace is broken/malicious, in which case userspace gets to keep the pieces.

#44 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 31 lines · Sean Christopherson
Quoted 17 lines · Zhi Wang

2) As TDX module doesn't provide contention-and-wait, I guess the following approach might have been discussed when designing this "retry".

KERNEL TDX MODULE

SEAMCALL A -> PATH A: Taking locks

SEAMCALL B -> PATH B: Contention on a lock

<- Return "operand busy"

SEAMCALL B   -|
              |  <- Wait on a kernel waitqueue
SEAMCALL B  <-|

SEAMCALL A <- PATH A: Return

Quoted 8 lines
SEAMCALL A   -|
              |  <- Wake up the waitqueue
SEMACALL A  <-| 

SEAMCALL B -> PATH B: Taking the locks
...

Why not this scheme wasn't chosen?

AFAIK, I don't think a waitqueue approach as ever been discussed publicly. Intel may have considered the idea internally, but I don't recall anything being proposed publically (though it's entirely possible I just missed the discussion).

Quoted 8 lines

Anways, I don't think a waitqueue would be a good fit, at least not for S-EPT management, which AFAICT is the only scenario where KVM does the arbitrary "retry X times and hope things work". If the contention occurs due to the TDX Module taking an S-EPT lock in VM-Enter, then KVM won't get a chance to do the "Wake up the waitqueue" action until the next VM-Exit, which IIUC is well after the TDX Module drops the S-EPT lock. In other words, immediately retrying and then punting the problem further up the stack in KVM does seem to be the least awful "solution" if there's contention.

Oh, the other important piece I forgot to mention is that dropping mmu_lock deep in KVM's MMU in order to wait isn't always an option. Most flows would play nice with dropping mmu_lock and sleeping, but some paths, e.g. from the mmu_notifier, (conditionally) disallow sleeping.

#45 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 41 lines · Sean Christopherson
Quoted 31 lines · Sean Christopherson
Quoted 17 lines · Zhi Wang

2) As TDX module doesn't provide contention-and-wait, I guess the following approach might have been discussed when designing this "retry".

KERNEL TDX MODULE

SEAMCALL A -> PATH A: Taking locks

SEAMCALL B -> PATH B: Contention on a lock

<- Return "operand busy"

SEAMCALL B   -|
              |  <- Wait on a kernel waitqueue
SEAMCALL B  <-|

SEAMCALL A <- PATH A: Return

Quoted 8 lines
SEAMCALL A   -|
              |  <- Wake up the waitqueue
SEMACALL A  <-| 

SEAMCALL B -> PATH B: Taking the locks
...

Why not this scheme wasn't chosen?

AFAIK, I don't think a waitqueue approach as ever been discussed publicly. Intel may have considered the idea internally, but I don't recall anything being proposed publically (though it's entirely possible I just missed the discussion).

Quoted 8 lines

Anways, I don't think a waitqueue would be a good fit, at least not for S-EPT management, which AFAICT is the only scenario where KVM does the arbitrary "retry X times and hope things work". If the contention occurs due to the TDX Module taking an S-EPT lock in VM-Enter, then KVM won't get a chance to do the "Wake up the waitqueue" action until the next VM-Exit, which IIUC is well after the TDX Module drops the S-EPT lock. In other words, immediately retrying and then punting the problem further up the stack in KVM does seem to be the least awful "solution" if there's contention.

Quoted 4 lines

Oh, the other important piece I forgot to mention is that dropping mmu_lock deep in KVM's MMU in order to wait isn't always an option. Most flows would play nice with dropping mmu_lock and sleeping, but some paths, e.g. from the mmu_notifier, (conditionally) disallow sleeping.

Could we do something similar to tdp_mmu_iter_cond_resched() but not simple busy retrying "X times", at least at those paths that can release mmu_lock()? Basically we treat TDX_OPERAND_BUSY as seamcall_needbreak(), similar to rwlock_needbreak(). I haven't thought about details though.

#46 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 42 lines · Kai
Quoted 41 lines · Sean Christopherson
Quoted 39 lines · Sean Christopherson
Quoted 17 lines · Zhi Wang

2) As TDX module doesn't provide contention-and-wait, I guess the following approach might have been discussed when designing this "retry".

KERNEL TDX MODULE

SEAMCALL A -> PATH A: Taking locks

SEAMCALL B -> PATH B: Contention on a lock

<- Return "operand busy"

SEAMCALL B   -|
              |  <- Wait on a kernel waitqueue
SEAMCALL B  <-|

SEAMCALL A <- PATH A: Return

Quoted 8 lines
SEAMCALL A   -|
              |  <- Wake up the waitqueue
SEMACALL A  <-| 

SEAMCALL B -> PATH B: Taking the locks
...

Why not this scheme wasn't chosen?

AFAIK, I don't think a waitqueue approach as ever been discussed publicly. Intel may have considered the idea internally, but I don't recall anything being proposed publically (though it's entirely possible I just missed the discussion).

Anways, I don't think a waitqueue would be a good fit, at least not for S-EPT management, which AFAICT is the only scenario where KVM does the arbitrary "retry X times and hope things work". If the contention occurs due to the TDX Module taking an S-EPT lock in VM-Enter, then KVM won't get a chance to do the "Wake up the waitqueue" action until the next VM-Exit, which IIUC is well after the TDX Module drops the S-EPT lock. In other words, immediately retrying and then punting the problem further up the stack in KVM does seem to be the least awful "solution" if there's contention.

Quoted 7 lines
Quoted 4 lines

Oh, the other important piece I forgot to mention is that dropping mmu_lock deep in KVM's MMU in order to wait isn't always an option. Most flows would play nice with dropping mmu_lock and sleeping, but some paths, e.g. from the mmu_notifier, (conditionally) disallow sleeping.

Could we do something similar to tdp_mmu_iter_cond_resched() but not simple busy retrying "X times", at least at those paths that can release mmu_lock()?

That's effectively what happens by unwinding up the stak with an error code. Eventually the page fault handler will get the error and retry the guest.

Quoted 2 lines

Basically we treat TDX_OPERAND_BUSY as seamcall_needbreak(), similar to rwlock_needbreak(). I haven't thought about details though.

I am strongly opposed to that approach. I do not want to pollute KVM's MMU code with a bunch of retry logic and error handling just because the TDX module is ultra paranoid and hostile to hypervisors.

The problematic scenario of faulting indefinitely on a single instruction should never happen under normal circumstances, and so KVM should treat such scenarios as attacks/breakage and pass the buck to userspace.

#47 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 54 lines · Sean Christopherson
Quoted 42 lines · Kai
Quoted 41 lines · Sean Christopherson
Quoted 39 lines · Sean Christopherson
Quoted 17 lines · Zhi Wang

2) As TDX module doesn't provide contention-and-wait, I guess the following approach might have been discussed when designing this "retry".

KERNEL TDX MODULE

SEAMCALL A -> PATH A: Taking locks

SEAMCALL B -> PATH B: Contention on a lock

<- Return "operand busy"

SEAMCALL B   -|
              |  <- Wait on a kernel waitqueue
SEAMCALL B  <-|

SEAMCALL A <- PATH A: Return

Quoted 8 lines
SEAMCALL A   -|
              |  <- Wake up the waitqueue
SEMACALL A  <-| 

SEAMCALL B -> PATH B: Taking the locks
...

Why not this scheme wasn't chosen?

AFAIK, I don't think a waitqueue approach as ever been discussed publicly. Intel may have considered the idea internally, but I don't recall anything being proposed publically (though it's entirely possible I just missed the discussion).

Anways, I don't think a waitqueue would be a good fit, at least not for S-EPT management, which AFAICT is the only scenario where KVM does the arbitrary "retry X times and hope things work". If the contention occurs due to the TDX Module taking an S-EPT lock in VM-Enter, then KVM won't get a chance to do the "Wake up the waitqueue" action until the next VM-Exit, which IIUC is well after the TDX Module drops the S-EPT lock. In other words, immediately retrying and then punting the problem further up the stack in KVM does seem to be the least awful "solution" if there's contention.

Quoted 7 lines
Quoted 4 lines

Oh, the other important piece I forgot to mention is that dropping mmu_lock deep in KVM's MMU in order to wait isn't always an option. Most flows would play nice with dropping mmu_lock and sleeping, but some paths, e.g. from the mmu_notifier, (conditionally) disallow sleeping.

Could we do something similar to tdp_mmu_iter_cond_resched() but not simple busy retrying "X times", at least at those paths that can release mmu_lock()?

That's effectively what happens by unwinding up the stak with an error code. Eventually the page fault handler will get the error and retry the guest.

Quoted 6 lines
Quoted 2 lines

Basically we treat TDX_OPERAND_BUSY as seamcall_needbreak(), similar to rwlock_needbreak(). I haven't thought about details though.

I am strongly opposed to that approach. I do not want to pollute KVM's MMU code with a bunch of retry logic and error handling just because the TDX module is ultra paranoid and hostile to hypervisors.

Right. But IIUC there's legal cases that SEPT SEAMCALL can return BUSY due to multiple threads trying to read/modify SEPT simultaneously in case of TDP MMU. For instance, parallel page faults on different vcpus on private pages. I believe this is the main reason to retry. We previously used spinlock around the SEAMCALLs to avoid, but looks that is not preferred.

Quoted 4 lines

The problematic scenario of faulting indefinitely on a single instruction should never happen under normal circumstances, and so KVM should treat such scenarios as attacks/breakage and pass the buck to userspace.

Totally agree zero-step attack can be treated KVM bug.

#48 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 19 lines · Kai
Quoted 18 lines · Sean Christopherson
Quoted 10 lines · Kai
Quoted 6 lines · Sean Christopherson
Quoted 1 line · Sean Christopherson

On Tue, Jan 17, 2023, Zhi Wang wrote:

Oh, the other important piece I forgot to mention is that dropping mmu_lock deep in KVM's MMU in order to wait isn't always an option. Most flows would play nice with dropping mmu_lock and sleeping, but some paths, e.g. from the mmu_notifier, (conditionally) disallow sleeping.

Could we do something similar to tdp_mmu_iter_cond_resched() but not simple busy retrying "X times", at least at those paths that can release mmu_lock()?

That's effectively what happens by unwinding up the stak with an error code. Eventually the page fault handler will get the error and retry the guest.

Quoted 2 lines

Basically we treat TDX_OPERAND_BUSY as seamcall_needbreak(), similar to rwlock_needbreak(). I haven't thought about details though.

Quoted 8 lines
Quoted 3 lines

I am strongly opposed to that approach. I do not want to pollute KVM's MMU code with a bunch of retry logic and error handling just because the TDX module is ultra paranoid and hostile to hypervisors.

Right. But IIUC there's legal cases that SEPT SEAMCALL can return BUSY due to multiple threads trying to read/modify SEPT simultaneously in case of TDP MMU. For instance, parallel page faults on different vcpus on private pages. I believe this is the main reason to retry.

Um, crud. I think there's a bigger issue. KVM always operates on its copy of the S-EPT tables and assumes the the real S-EPT tables will always be synchronized with KVM's mirror. That assumption doesn't hold true without serializing SEAMCALLs in some way. E.g. if a SPTE is zapped and mapped at the same time, we can end up with:

vCPU0 vCPU1
===== =====
mirror[x] = xyz
old_spte = mirror[x] mirror[x] = REMOVED_SPTE sept[x] = REMOVED_SPTE
sept[x] = xyz

In other words, when mmu_lock is held for read, KVM relies on atomic SPTE updates. With the mirror=>s-ept scheme, updates are no longer atomic and everything falls apart.

Gracefully retrying only papers over the visible failures, the really problematic scenarios are where multiple updates race and _don't_ trigger conflicts in the TDX module.

Quoted 2 lines

We previously used spinlock around the SEAMCALLs to avoid, but looks that is not preferred.

That doesn't address the race above either. And even if it did, serializing all S-EPT SEAMCALLs for a VM is not an option, at least not in the long term.

The least invasive idea I have is expand the TDP MMU's concept of "frozen" SPTEs and freeze (a.k.a. lock) the SPTE (KVM's mirror) until the corresponding S-EPT update completes.

The other idea is to scrap the mirror concept entirely, though I gotta imagine that would provide pretty awful performance.

arch/x86/kvm/mmu/spte.h +2 -2
index 0d8deefee66c..bcb398e71475 100644
@@ -198,9 +198,9 @@ extern u64 __read_mostly shadow_nonpresent_or_rsvd_mask;
 /* Removed SPTEs must not be misconstrued as shadow present PTEs. */
 static_assert(!(REMOVED_SPTE & SPTE_MMU_PRESENT_MASK));
 
-static inline bool is_removed_spte(u64 spte)
+static inline bool is_frozen_spte(u64 spte)
 {
-       return spte == REMOVED_SPTE;
+       return spte == REMOVED_SPTE || spte & FROZEN_SPTE;
 }
 
 /* Get an SPTE's index into its parent's page table (and the spt array). */
arch/x86/kvm/mmu/tdp_mmu.c +6 -0
index bba33aea0fb0..7f34eccadf98 100644
@@ -651,6 +651,9 @@ static inline int tdp_mmu_set_spte_atomic(struct kvm *kvm,
 
        lockdep_assert_held_read(&kvm->mmu_lock);
 
+       if (<is TDX> && new_spte != REMOVED_SPTE)
+               new_spte |= FROZEN_SPTE;
+
        /*
         * Note, fast_pf_fix_direct_spte() can also modify TDP MMU SPTEs and
         * does not hold the mmu_lock.
@@ -662,6 +665,9 @@ static inline int tdp_mmu_set_spte_atomic(struct kvm *kvm,
                              new_spte, iter->level, true);
        handle_changed_spte_acc_track(iter->old_spte, new_spte, iter->level);
 
+       if (<is TDX> && new_spte != REMOVED_SPTE)
+               __kvm_tdp_mmu_write_spte(iter->sptep, new_spte);
+
        return 0;
 }
 
#49 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 34 lines · Sean Christopherson
Quoted 19 lines · Kai
Quoted 18 lines · Sean Christopherson
Quoted 10 lines · Kai
Quoted 6 lines · Sean Christopherson
Quoted 1 line · Sean Christopherson

On Tue, Jan 17, 2023, Zhi Wang wrote:

Oh, the other important piece I forgot to mention is that dropping mmu_lock deep in KVM's MMU in order to wait isn't always an option. Most flows would play nice with dropping mmu_lock and sleeping, but some paths, e.g. from the mmu_notifier, (conditionally) disallow sleeping.

Could we do something similar to tdp_mmu_iter_cond_resched() but not simple busy retrying "X times", at least at those paths that can release mmu_lock()?

That's effectively what happens by unwinding up the stak with an error code. Eventually the page fault handler will get the error and retry the guest.

Quoted 2 lines

Basically we treat TDX_OPERAND_BUSY as seamcall_needbreak(), similar to rwlock_needbreak(). I haven't thought about details though.

Quoted 8 lines
Quoted 3 lines

I am strongly opposed to that approach. I do not want to pollute KVM's MMU code with a bunch of retry logic and error handling just because the TDX module is ultra paranoid and hostile to hypervisors.

Right. But IIUC there's legal cases that SEPT SEAMCALL can return BUSY due to multiple threads trying to read/modify SEPT simultaneously in case of TDP MMU. For instance, parallel page faults on different vcpus on private pages. I believe this is the main reason to retry.

Um, crud. I think there's a bigger issue. KVM always operates on its copy of the S-EPT tables and assumes the the real S-EPT tables will always be synchronized with KVM's mirror. That assumption doesn't hold true without serializing SEAMCALLs in some way. E.g. if a SPTE is zapped and mapped at the same time, we can end up with:

Quoted 7 lines

vCPU0 vCPU1
===== =====
mirror[x] = xyz
old_spte = mirror[x] mirror[x] = REMOVED_SPTE sept[x] = REMOVED_SPTE
sept[x] = xyz

IIUC this case cannot happen, as the two steps in the vcpu0 are within read lock, which prevents from vcpu1, which holds the write lock during zapping SPTE.

#50 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 43 lines · Kai
Quoted 34 lines · Sean Christopherson
Quoted 19 lines · Kai
Quoted 18 lines · Sean Christopherson
Quoted 10 lines · Kai
Quoted 6 lines · Sean Christopherson
Quoted 1 line · Sean Christopherson

On Tue, Jan 17, 2023, Zhi Wang wrote:

Oh, the other important piece I forgot to mention is that dropping mmu_lock deep in KVM's MMU in order to wait isn't always an option. Most flows would play nice with dropping mmu_lock and sleeping, but some paths, e.g. from the mmu_notifier, (conditionally) disallow sleeping.

Could we do something similar to tdp_mmu_iter_cond_resched() but not simple busy retrying "X times", at least at those paths that can release mmu_lock()?

That's effectively what happens by unwinding up the stak with an error code. Eventually the page fault handler will get the error and retry the guest.

Quoted 2 lines

Basically we treat TDX_OPERAND_BUSY as seamcall_needbreak(), similar to rwlock_needbreak(). I haven't thought about details though.

Quoted 8 lines
Quoted 3 lines

I am strongly opposed to that approach. I do not want to pollute KVM's MMU code with a bunch of retry logic and error handling just because the TDX module is ultra paranoid and hostile to hypervisors.

Right. But IIUC there's legal cases that SEPT SEAMCALL can return BUSY due to multiple threads trying to read/modify SEPT simultaneously in case of TDP MMU. For instance, parallel page faults on different vcpus on private pages. I believe this is the main reason to retry.

Um, crud. I think there's a bigger issue. KVM always operates on its copy of the S-EPT tables and assumes the the real S-EPT tables will always be synchronized with KVM's mirror. That assumption doesn't hold true without serializing SEAMCALLs in some way. E.g. if a SPTE is zapped and mapped at the same time, we can end up with:

Quoted 7 lines

vCPU0 vCPU1
===== =====
mirror[x] = xyz
old_spte = mirror[x] mirror[x] = REMOVED_SPTE sept[x] = REMOVED_SPTE
sept[x] = xyz

Quoted 2 lines

IIUC this case cannot happen, as the two steps in the vcpu0 are within read lock, which prevents from vcpu1, which holds the write lock during zapping SPTE.

Zapping SPTEs can happen while holding mmu_lock for read, e.g. see the bug fixed by commit 21a36ac6b6c7 ("KVM: x86/mmu: Re-check under lock that TDP MMU SP hugepage is disallowed").

#51 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 44 lines · Sean Christopherson
Quoted 43 lines · Kai
Quoted 41 lines · Sean Christopherson
Quoted 19 lines · Kai
Quoted 18 lines · Sean Christopherson
Quoted 10 lines · Kai
Quoted 6 lines · Sean Christopherson
Quoted 1 line · Sean Christopherson

On Tue, Jan 17, 2023, Zhi Wang wrote:

Oh, the other important piece I forgot to mention is that dropping mmu_lock deep in KVM's MMU in order to wait isn't always an option. Most flows would play nice with dropping mmu_lock and sleeping, but some paths, e.g. from the mmu_notifier, (conditionally) disallow sleeping.

Could we do something similar to tdp_mmu_iter_cond_resched() but not simple busy retrying "X times", at least at those paths that can release mmu_lock()?

That's effectively what happens by unwinding up the stak with an error code. Eventually the page fault handler will get the error and retry the guest.

Quoted 2 lines

Basically we treat TDX_OPERAND_BUSY as seamcall_needbreak(), similar to rwlock_needbreak(). I haven't thought about details though.

Quoted 8 lines
Quoted 3 lines

I am strongly opposed to that approach. I do not want to pollute KVM's MMU code with a bunch of retry logic and error handling just because the TDX module is ultra paranoid and hostile to hypervisors.

Right. But IIUC there's legal cases that SEPT SEAMCALL can return BUSY due to multiple threads trying to read/modify SEPT simultaneously in case of TDP MMU. For instance, parallel page faults on different vcpus on private pages. I believe this is the main reason to retry.

Um, crud. I think there's a bigger issue. KVM always operates on its copy of the S-EPT tables and assumes the the real S-EPT tables will always be synchronized with KVM's mirror. That assumption doesn't hold true without serializing SEAMCALLs in some way. E.g. if a SPTE is zapped and mapped at the same time, we can end up with:

vCPU0 vCPU1
===== =====
mirror[x] = xyz
old_spte = mirror[x] mirror[x] = REMOVED_SPTE sept[x] = REMOVED_SPTE
sept[x] = xyz

Quoted 6 lines
Quoted 2 lines

IIUC this case cannot happen, as the two steps in the vcpu0 are within read lock, which prevents from vcpu1, which holds the write lock during zapping SPTE.

Zapping SPTEs can happen while holding mmu_lock for read, e.g. see the bug fixed by commit 21a36ac6b6c7 ("KVM: x86/mmu: Re-check under lock that TDP MMU SP hugepage is disallowed").

Sorry, I now recall that I once had patch to change write path to hold write_lock() for TDX guest to avoid such problem. I thought it was upstream behaviour :)

#52 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 48 lines · Kai
Quoted 47 lines · Sean Christopherson
Quoted 45 lines · Kai
Quoted 34 lines · Sean Christopherson
Quoted 19 lines · Kai
Quoted 18 lines · Sean Christopherson
Quoted 10 lines · Kai
Quoted 6 lines · Sean Christopherson
Quoted 1 line · Sean Christopherson

On Tue, Jan 17, 2023, Zhi Wang wrote:

Oh, the other important piece I forgot to mention is that dropping mmu_lock deep in KVM's MMU in order to wait isn't always an option. Most flows would play nice with dropping mmu_lock and sleeping, but some paths, e.g. from the mmu_notifier, (conditionally) disallow sleeping.

Could we do something similar to tdp_mmu_iter_cond_resched() but not simple busy retrying "X times", at least at those paths that can release mmu_lock()?

That's effectively what happens by unwinding up the stak with an error code. Eventually the page fault handler will get the error and retry the guest.

Quoted 2 lines

Basically we treat TDX_OPERAND_BUSY as seamcall_needbreak(), similar to rwlock_needbreak(). I haven't thought about details though.

Quoted 8 lines
Quoted 3 lines

I am strongly opposed to that approach. I do not want to pollute KVM's MMU code with a bunch of retry logic and error handling just because the TDX module is ultra paranoid and hostile to hypervisors.

Right. But IIUC there's legal cases that SEPT SEAMCALL can return BUSY due to multiple threads trying to read/modify SEPT simultaneously in case of TDP MMU. For instance, parallel page faults on different vcpus on private pages. I believe this is the main reason to retry.

Um, crud. I think there's a bigger issue. KVM always operates on its copy of the S-EPT tables and assumes the the real S-EPT tables will always be synchronized with KVM's mirror. That assumption doesn't hold true without serializing SEAMCALLs in some way. E.g. if a SPTE is zapped and mapped at the same time, we can end up with:

Quoted 7 lines

vCPU0 vCPU1
===== =====
mirror[x] = xyz
old_spte = mirror[x] mirror[x] = REMOVED_SPTE sept[x] = REMOVED_SPTE
sept[x] = xyz

IIUC this case cannot happen, as the two steps in the vcpu0 are within read lock, which prevents from vcpu1, which holds the write lock during zapping SPTE.

Quoted 7 lines
Quoted 3 lines

Zapping SPTEs can happen while holding mmu_lock for read, e.g. see the bug fixed by commit 21a36ac6b6c7 ("KVM: x86/mmu: Re-check under lock that TDP MMU SP hugepage is disallowed").

Sorry, I now recall that I once had patch to change write path to hold write_lock() for TDX guest to avoid such problem. I thought it was upstream behaviour :)

I mean change the zap PTE path to hold write lock. :)

#53 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

In reply to Sean Christopherson
Quoted 3 lines · Sean Christopherson

The least invasive idea I have is expand the TDP MMU's concept of "frozen" SPTEs and freeze (a.k.a. lock) the SPTE (KVM's mirror) until the corresponding S-EPT update completes.

This will introduce another "having-to-wait while SPTE is frozen" problem I think, which IIUC means (one way is) you have to do some loop and retry, perhaps similar to yield_safe.

Quoted 3 lines

The other idea is to scrap the mirror concept entirely, though I gotta imagine that would provide pretty awful performance.

Right. I don't think this is a good option.

#54 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 8 lines · Kai
Quoted 3 lines · Sean Christopherson

The least invasive idea I have is expand the TDP MMU's concept of "frozen" SPTEs and freeze (a.k.a. lock) the SPTE (KVM's mirror) until the corresponding S-EPT update completes.

This will introduce another "having-to-wait while SPTE is frozen" problem I think, which IIUC means (one way is) you have to do some loop and retry, perhaps similar to yield_safe.

Yes, but because the TDP MMU already freezes SPTEs (just for a shorter duration), I'm 99% sure all of the affected flows already know how to yield/bail when necessary.

The problem with the zero-step mitigation is that it could (theoretically) cause a "busy" error on literally any accesses, which makes it infeasible for KVM to have sane behavior. E.g. freezing SPTEs to avoid the ordering issues isn't necessary when holding mmu_lock for write, whereas the zero-step madness brings everything into play.

#55 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 11 lines · Sean Christopherson
Quoted 8 lines · Kai
Quoted 3 lines · Sean Christopherson

The least invasive idea I have is expand the TDP MMU's concept of "frozen" SPTEs and freeze (a.k.a. lock) the SPTE (KVM's mirror) until the corresponding S-EPT update completes.

This will introduce another "having-to-wait while SPTE is frozen" problem I think, which IIUC means (one way is) you have to do some loop and retry, perhaps similar to yield_safe.

Quoted 8 lines

Yes, but because the TDP MMU already freezes SPTEs (just for a shorter duration), I'm 99% sure all of the affected flows already know how to yield/bail when necessary.

The problem with the zero-step mitigation is that it could (theoretically) cause a "busy" error on literally any accesses, which makes it infeasible for KVM to have sane behavior. E.g. freezing SPTEs to avoid the ordering issues isn't necessary when holding mmu_lock for write, whereas the zero-step madness brings everything into play.

(I'm still ramping up on TDX so apologies in advance if the following is totally off base.)

The complexity, and to a lesser extent the memory overhead, of mirroring Secure EPT tables with the TDP MMU makes me wonder if it is really worth it. Especially since the initial TDX support has so many constraints that would seem to allow a simpler implementation: all private memory is pinned, no live migration support, no test/clear young notifiers, etc.

For the initial version of KVM TDX support, what if we implemented the Secure EPT management entirely off to the side? i.e. Not on top of the TDP MMU. For example, write TDX-specific routines for:

- Fully populating the Secure EPT tree some time during VM creation.
- Tearing down the Secure EPT tree during VM destruction.
- Support for unmapping/mapping specific regions of the Secure EPT
tree for private<->shared conversions.

With that in place, KVM never would need to handle a fault on a Secure EPT mapping. Any fault (e.g. due to an in-progress private<->shared conversion) can just return back to the guest to retry the memory access until the operation is complete.

If we start with only supporting 4K pages in the Secure EPT, the Secure EPT routines described above would be almost trivial to implement. Huge Pages would add some complexity, but I don't think it would be terrible. Concurrency can be handled with a single lock since we don't have to worry about concurrent faulting.

This would avoid having TDX add a bunch of complexity to the TDP MMU (which would only be used for shared mappings). If and when we want to have more complicated memory management for TDX private mappings, we could revisit TDP MMU integration. But I think this design could even get us to the point of supporting Dirty Logging (where the only fault KVM would have to handle for TDX private mappings would be write-protection faults). I'm not sure it would work for Demand-Paging (at least the performance would not be great behind a single lock), but we can cross that bridge when we get there.

#56 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 7 lines · David Matlack
Quoted 6 lines · Sean Christopherson
Quoted 4 lines · Kai
Quoted 3 lines · Sean Christopherson

The least invasive idea I have is expand the TDP MMU's concept of "frozen" SPTEs and freeze (a.k.a. lock) the SPTE (KVM's mirror) until the corresponding S-EPT update completes.a

Doh, the proposed patches already do a freeze+unfreeze. Sorry, I obviously didn't read the most recent changelog and haven't looked at the code in a few versions.

Quoted 23 lines
Quoted 4 lines
Quoted 3 lines

This will introduce another "having-to-wait while SPTE is frozen" problem I think, which IIUC means (one way is) you have to do some loop and retry, perhaps similar to yield_safe.

Quoted 8 lines

Yes, but because the TDP MMU already freezes SPTEs (just for a shorter duration), I'm 99% sure all of the affected flows already know how to yield/bail when necessary.

The problem with the zero-step mitigation is that it could (theoretically) cause a "busy" error on literally any accesses, which makes it infeasible for KVM to have sane behavior. E.g. freezing SPTEs to avoid the ordering issues isn't necessary when holding mmu_lock for write, whereas the zero-step madness brings everything into play.

(I'm still ramping up on TDX so apologies in advance if the following is totally off base.)

The complexity, and to a lesser extent the memory overhead, of mirroring Secure EPT tables with the TDP MMU makes me wonder if it is really worth it. Especially since the initial TDX support has so many constraints that would seem to allow a simpler implementation: all private memory is pinned, no live migration support, no test/clear young notifiers, etc.

Quoted 5 lines

For the initial version of KVM TDX support, what if we implemented the Secure EPT management entirely off to the side? i.e. Not on top of the TDP MMU. For example, write TDX-specific routines for:

- Fully populating the Secure EPT tree some time during VM creation.

Fully populating S-EPT is likely not a viable option for performance reasons. The number of SEAMCALLS needed for a large VM would be quite enormous, and to avoid faults entirely the backing memory would also need to be pre-allocated.

Quoted 8 lines

- Tearing down the Secure EPT tree during VM destruction.
- Support for unmapping/mapping specific regions of the Secure EPT
tree for private<->shared conversions.

With that in place, KVM never would need to handle a fault on a Secure EPT mapping. Any fault (e.g. due to an in-progress private<->shared conversion) can just return back to the guest to retry the memory access until the operation is complete.

I don't think this will work. Or at least, it's not that simple. TDX and SNP both require the host to support implicit conversions from shared => private, i.e. KVM needs to be able to handle faults on private memory at any time. KVM could rely on the attributes xarray to know if KVM should resume the guest or exit to userspace, but at some point KVM needs to know whether or an entry has been installed. We could work around that by adding a hooking to "set attributes" to populate S-EPT entries, but I'm not sure that would yield a simpler implementation without sacrificing performance.

Thinking more about this, I do believe there is a simpler approach than freezing S-EPT entries. If we tweak the TDP MMU rules for TDX to require mmu_lock be held for write when overwiting a present SPTE (with a live VM), then the problematic cases go away. As you point out, all of the restrictions on TDX private memory means we're most of the way there.

Explicit unmap flows already take mmu_lock for write, zapping/splitting for dirty logging isn't a thing (yet), and the S-EPT root is fixed, i.e. kvm_tdp_mmu_put_root() should never free S-EPT tables until the VM is dead.

In other words, the only problematic flow is kvm_tdp_mmu_map(). Specifically, KVM will overwrite a present SPTE only to demote/promote an existing entry, i.e. to split a hugepage or collapse into a hugepage. And I believe it's not too difficult to ensure KVM never does in-place promotion/demotion:

- Private memory doesn't rely on host userspace page tables, i.e. KVM won't try to promote to a hugepage because a hugepage was created in the host.

- Private memory doesn't (yet) support page migration, so again KVM won't try to promote because a hugepage is suddenly possible.

- Shadow paging isn't compatible, i.e. disallow_lpage won't change dynamically except for when there are mixed attributes, and toggling private/shared in the attributes requires zapping with mmu_lock held for write.

- The idiotic non-coherent DMA + guest MTRR interaction can be disallowed (which I assume is already the case for TDX).

- Disallow toggling the NX hugepage mitigation when TDX is allowed.

Races to install leaf SPTEs will yield a "loser" either on the spurious check in tdp_mmu_map_handle_target_level():

	if (new_spte == iter->old_spte)
		ret = RET_PF_SPURIOUS;

or on the CMPXCHG in tdp_mmu_set_spte_atomic().

So I _think_ the race I described can't actually happen in practice, and if it does happen, then we have a bug somewhere else. Though that raises the question of why the proposed patches do the freeze+unfreeze. If it's just the VM destroy case, then that should be easy enough to special case.

Intel folks,

Do you happen to know exactly what scenario prompted adding the freeze+unfreeze code? Is there something I'm forgetting/missing, or is it possible we can go with a simpler implementation?

#57 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 7 lines

Intel folks,

Do you happen to know exactly what scenario prompted adding the freeze+unfreeze code? Is there something I'm forgetting/missing, or is it possible we can go with a simpler implementation?

It's documented in the "TDX TDP MMU design doc" patch:

+TDX concurrent populating
+-------------------------
......
+
+Without freezing the entry, the following race can happen. Suppose two vcpus +are faulting on the same GPA and the 2M and 4K level entries aren't populated +yet.
+
+* vcpu 1: update 2M level EPT entry
+* vcpu 2: update 4K level EPT entry
+* vcpu 2: TDX SEAMCALL to update 4K secure EPT entry => error
+* vcpu 1: TDX SEAMCALL to update 2M secure EPT entry
+

( I guess such material will be more useful in the comment. And perhaps we can get rid of the "TDX TDP MMU design doc" patch in this series at least for now as probably nobody will look at it :-) )

#58 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 12 lines · Kai
Quoted 7 lines

Intel folks,

Do you happen to know exactly what scenario prompted adding the freeze+unfreeze code? Is there something I'm forgetting/missing, or is it possible we can go with a simpler implementation?

It's documented in the "TDX TDP MMU design doc" patch:

(quoted diff) +2 -0
+TDX concurrent populating
+-------------------------

Review

Quoted 10 lines
(quoted diff) +9 -0
......
+
+Without freezing the entry, the following race can happen.  Suppose two vcpus
+are faulting on the same GPA and the 2M and 4K level entries aren't populated
+yet.
+
+* vcpu 1: update 2M level EPT entry
+* vcpu 2: update 4K level EPT entry
+* vcpu 2: TDX SEAMCALL to update 4K secure EPT entry => error
+* vcpu 1: TDX SEAMCALL to update 2M secure EPT entry

Ooh, the problem isn't that two SEAMCALLs to the same entry get out of order, it's that SEAMCALLs operating on child entries can race ahead of the parent. Hrm.

TDX also has the annoying-but-understandable restriction that leafs need to be removed before parents. A not-yet-relevant complication on that front is that the TDP MMU's behavior of recursively removing children means we also have to worry about PRESENT => !PRESENT transitions, e.g. zapping a child because the parent is being removed can race with a different vCPU try to populate the child (because the vCPU handling a page fault could have seen the PRESENT parent).

I think there's an opportunity and motivation to improve the TDP MMU as a whole on this front though. Rather than recursively zap children in handle_removed_pt(), we can use the RCU callback to queue the page table for removal. Setting the parent (target page table) !PRESENT and flushing the TLBs ensures that all children are unreachable, i.e. KVM doesn't need to immediately set children !PRESENT. Unlike the shadow MMU, which maintains a hash table of shadow pages, once a parent page table is removed from the TDP MMU, its children are unreachabled.

The RCU callback must run in near-constant time, but that's easy to solve as we already have a workqueue for zapping page tables, i.e. the RCU callback can simply add the target page to the zap workqueue. That would also allow for a (very minor) simplification of other TDP MMU code: tdp_mmu_zap_root() wouldn't needed to zap in two passes since zapping children of the top-level SPTEs would be deferred to the workqueue.

Back to TDX, to play nice with the restriction that parents are removed only after children are removed, I believe KVM can use TDH.MEM.RANGE.BLOCK to make the parent !PRESENT. That will effectively prune the S-EPT entry and all its children, and the RCU callback will again ensure all in-flight SEAMCALLs for the children complete before KVM actually tries to zap the children.

And if we rework zapping page tables, I suspect we can also address David's concern (and my not-yet-voiced concern) about polluting the TDP MMU code with logic that is necessary only for S-EPT (freezing SPTEs before populating them). Rather than update S-EPT _after_ the TDP MMU SPTE, do the S-EPT update first, i.e. invoke the KVM TDX hook before try_cmpxchg64() (or maybe instead of?). That way KVM TDX can freeze the to-be-installed SPTE without common TDP MMU needing to be aware of the change.

Quoted 3 lines

( I guess such material will be more useful in the comment. And perhaps we can get rid of the "TDX TDP MMU design doc" patch in this series at least for now as probably nobody will look at it :-) )

Please keep the design doc, I'll definitely read it. I'm just chasing too many things at the moment and haven't given the TDX series a proper review, i.e. haven't even glanced through all of the patches or even the shortlog.

#59 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 27 lines · Sean Christopherson
Quoted 12 lines · Kai
Quoted 7 lines

Intel folks,

Do you happen to know exactly what scenario prompted adding the freeze+unfreeze code? Is there something I'm forgetting/missing, or is it possible we can go with a simpler implementation?

It's documented in the "TDX TDP MMU design doc" patch:

(quoted diff) +2 -0
+TDX concurrent populating
+-------------------------

Review

Quoted 10 lines
(quoted diff) +9 -0
......
+
+Without freezing the entry, the following race can happen.  Suppose two vcpus
+are faulting on the same GPA and the 2M and 4K level entries aren't populated
+yet.
+
+* vcpu 1: update 2M level EPT entry
+* vcpu 2: update 4K level EPT entry
+* vcpu 2: TDX SEAMCALL to update 4K secure EPT entry => error
+* vcpu 1: TDX SEAMCALL to update 2M secure EPT entry

Ooh, the problem isn't that two SEAMCALLs to the same entry get out of order, it's that SEAMCALLs operating on child entries can race ahead of the parent. Hrm.

Quoted 6 lines

TDX also has the annoying-but-understandable restriction that leafs need to be removed before parents. A not-yet-relevant complication on that front is that the TDP MMU's behavior of recursively removing children means we also have to worry about PRESENT => !PRESENT transitions, e.g. zapping a child because the parent is being removed can race with a different vCPU try to populate the child (because the vCPU handling a page fault could have seen the PRESENT parent).

Yes.

Quoted 8 lines

I think there's an opportunity and motivation to improve the TDP MMU as a whole on this front though. Rather than recursively zap children in handle_removed_pt(), we can use the RCU callback to queue the page table for removal. Setting the parent (target page table) !PRESENT and flushing the TLBs ensures that all children are unreachable, i.e. KVM doesn't need to immediately set children !PRESENT. Unlike the shadow MMU, which maintains a hash table of shadow pages, once a parent page table is removed from the TDP MMU, its children are unreachabled.

Do you mean something like (pseudo):

	rcu_callback(&removed_sp->rcu_head, handle_removed_pt);
Quoted 7 lines

The RCU callback must run in near-constant time, but that's easy to solve as we already have a workqueue for zapping page tables, i.e. the RCU callback can simply add the target page to the zap workqueue. That would also allow for a (very minor) simplification of other TDP MMU code: tdp_mmu_zap_root() wouldn't needed to zap in two passes since zapping children of the top-level SPTEs would be deferred to the workqueue.

Do you mean zapping the entire page table (from root) doesn't need to be in RCU read-critical section, but can/should be done after grace period? I think this makes sense since zapping entire root must happen when root is already invalid, which cannot be used anymore when the new faults come in?

Quoted 6 lines

Back to TDX, to play nice with the restriction that parents are removed only after children are removed, I believe KVM can use TDH.MEM.RANGE.BLOCK to make the parent !PRESENT. That will effectively prune the S-EPT entry and all its children, and the RCU callback will again ensure all in-flight SEAMCALLs for the children complete before KVM actually tries to zap the children.

Reading the spec, it seems TDH.MEM.RANGE.BLOCK only sets the Secure EPT entry which points to the entire range as "blocked", but won't go down until leaf to mark all EPT entries as "blocked", which makes sense anyway.

But it seems TDH.MEM.PAGE.REMOVE and TDH.MEM.SEPT.REMOVE both only checks whether that target EPT entry is "blocked", but doesn't check whether any parent has been marked as "blocked". Not sure whether this will be a problem. But anyway if this is a problem, we perhaps can get TDX module to fix.

Quoted 7 lines

And if we rework zapping page tables, I suspect we can also address David's concern (and my not-yet-voiced concern) about polluting the TDP MMU code with logic that is necessary only for S-EPT (freezing SPTEs before populating them). Rather than update S-EPT _after_ the TDP MMU SPTE, do the S-EPT update first, i.e. invoke the KVM TDX hook before try_cmpxchg64() (or maybe instead of?). That way KVM TDX can freeze the to-be-installed SPTE without common TDP MMU needing to be aware of the change.

I don't quite understand how putting SEAMCALL before the try_cmpxchg64() can work. Let's say one thread is populating a mapping and another is zapping it. The populating thread makes SEAMCALL successfully but then try_cmpxchg64() fails, in this case how to proceed?

Quoted 8 lines
Quoted 4 lines

( I guess such material will be more useful in the comment. And perhaps we can get rid of the "TDX TDP MMU design doc" patch in this series at least for now as probably nobody will look at it :-) )

Please keep the design doc, I'll definitely read it. I'm just chasing too many things at the moment and haven't given the TDX series a proper review, i.e. haven't even glanced through all of the patches or even the shortlog.

#60 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 9 lines · Kai
Quoted 7 lines · Sean Christopherson

I think there's an opportunity and motivation to improve the TDP MMU as a whole on this front though. Rather than recursively zap children in handle_removed_pt(), we can use the RCU callback to queue the page table for removal. Setting the parent (target page table) !PRESENT and flushing the TLBs ensures that all children are unreachable, i.e. KVM doesn't need to immediately set children !PRESENT. Unlike the shadow MMU, which maintains a hash table of shadow pages, once a parent page table is removed from the TDP MMU, its children are unreachabled.

Quoted 3 lines

Do you mean something like (pseudo):

	rcu_callback(&removed_sp->rcu_head, handle_removed_pt);

Yep.

Quoted 11 lines
Quoted 6 lines

The RCU callback must run in near-constant time, but that's easy to solve as we already have a workqueue for zapping page tables, i.e. the RCU callback can simply add the target page to the zap workqueue. That would also allow for a (very minor) simplification of other TDP MMU code: tdp_mmu_zap_root() wouldn't needed to zap in two passes since zapping children of the top-level SPTEs would be deferred to the workqueue.

Do you mean zapping the entire page table (from root) doesn't need to be in RCU read-critical section, but can/should be done after grace period? I think this makes sense since zapping entire root must happen when root is already invalid, which cannot be used anymore when the new faults come in?

Yes, minus the "from root" restriction. When a page table (call it "branch" to continue the analogy) PTE has been zapped/blocked ("pruned"), KVM just needs to wait for all potential readers to go away. That guarantee is provided by RCU; software walkers, i.e. KVM itself, are required to hold RCU, and hardware walkers, i.e. vCPUs running in the guest, are protected by proxy as the zapper ("arborist"?) is required to hold RCU until all running vCPUs have been kicked.

In other words, once the PTE is zapped/blocked (branch is pruned), it's completely removed from the paging tree and no other tasks can access the branch (page table and its children). I.e. the only remaining reference to the branch is the pointer handed to the RCU callback. That means the RCU callback has exclusive access to the branch, i.e. can operate as if it were holding mmu_lock for write. Furthermore, the RCU callback also doesn't need to flush TLBs because that was again done when pruning the branch.

It's the same idea that KVM already uses for root SPs, the only difference is how KVM determines that there is exactly one entity that holds a reference to the SP.

Quoted 6 lines
Quoted 5 lines

Back to TDX, to play nice with the restriction that parents are removed only after children are removed, I believe KVM can use TDH.MEM.RANGE.BLOCK to make the parent !PRESENT. That will effectively prune the S-EPT entry and all its children, and the RCU callback will again ensure all in-flight SEAMCALLs for the children complete before KVM actually tries to zap the children.

Quoted 8 lines

Reading the spec, it seems TDH.MEM.RANGE.BLOCK only sets the Secure EPT entry which points to the entire range as "blocked", but won't go down until leaf to mark all EPT entries as "blocked", which makes sense anyway.

But it seems TDH.MEM.PAGE.REMOVE and TDH.MEM.SEPT.REMOVE both only checks whether that target EPT entry is "blocked", but doesn't check whether any parent has been marked as "blocked". Not sure whether this will be a problem. But anyway if this is a problem, we perhaps can get TDX module to fix.

Oh, I didn't mean to suggest KVM skip TDH.MEM.RANGE.BLOCK for children, I simply forgot that all S-EPT entries need to be blocked before they can be removed.

Quoted 11 lines
Quoted 6 lines

And if we rework zapping page tables, I suspect we can also address David's concern (and my not-yet-voiced concern) about polluting the TDP MMU code with logic that is necessary only for S-EPT (freezing SPTEs before populating them). Rather than update S-EPT _after_ the TDP MMU SPTE, do the S-EPT update first, i.e. invoke the KVM TDX hook before try_cmpxchg64() (or maybe instead of?). That way KVM TDX can freeze the to-be-installed SPTE without common TDP MMU needing to be aware of the change.

I don't quite understand how putting SEAMCALL before the try_cmpxchg64() can work. Let's say one thread is populating a mapping and another is zapping it. The populating thread makes SEAMCALL successfully but then try_cmpxchg64() fails, in this case how to proceed?

Ah, sorry, that was unclear. By "invoke the KVM TDX hook" I didn't mean "do the SEAMCALL", I meant KVM TDX could do its own manipulation of the KVM-managed SPTEs before the common/standard flow. E.g. something like:

	if (kvm_x86_ops.set_private_spte && private)
		r = static_call(kvm_x86_set_private_spte(...)
	else
		r = try_cmpxchg64(...) ? 0 : -EBUSY;

so that the common code doesn't need to do, or even be aware of, the freezing. Then I think we just need another hook in handle_removed_pt(), or maybe in what is currently __kvm_tdp_mmu_write_spte()?

I.e. fully replace the "write" operations in the TDP MMU instead of trying to smush S-EPT's requirements into the common path.

#61 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 28 lines · Sean Christopherson
Quoted 9 lines · Kai
Quoted 7 lines · Sean Christopherson

I think there's an opportunity and motivation to improve the TDP MMU as a whole on this front though. Rather than recursively zap children in handle_removed_pt(), we can use the RCU callback to queue the page table for removal. Setting the parent (target page table) !PRESENT and flushing the TLBs ensures that all children are unreachable, i.e. KVM doesn't need to immediately set children !PRESENT. Unlike the shadow MMU, which maintains a hash table of shadow pages, once a parent page table is removed from the TDP MMU, its children are unreachabled.

Quoted 3 lines

Do you mean something like (pseudo):

	rcu_callback(&removed_sp->rcu_head, handle_removed_pt);

Yep.

Quoted 11 lines
Quoted 6 lines

The RCU callback must run in near-constant time, but that's easy to solve as we already have a workqueue for zapping page tables, i.e. the RCU callback can simply add the target page to the zap workqueue. That would also allow for a (very minor) simplification of other TDP MMU code: tdp_mmu_zap_root() wouldn't needed to zap in two passes since zapping children of the top-level SPTEs would be deferred to the workqueue.

Do you mean zapping the entire page table (from root) doesn't need to be in RCU read-critical section, but can/should be done after grace period? I think this makes sense since zapping entire root must happen when root is already invalid, which cannot be used anymore when the new faults come in?

Quoted 6 lines

Yes, minus the "from root" restriction. When a page table (call it "branch" to continue the analogy) PTE has been zapped/blocked ("pruned"), KVM just needs to wait for all potential readers to go away. That guarantee is provided by RCU; software walkers, i.e. KVM itself, are required to hold RCU, and hardware walkers, i.e. vCPUs running in the guest, are protected by proxy as the zapper ("arborist"?) is required to hold RCU until all running vCPUs have been kicked.

Yes.

Quoted 11 lines

In other words, once the PTE is zapped/blocked (branch is pruned), it's completely removed from the paging tree and no other tasks can access the branch (page table and its children). I.e. the only remaining reference to the branch is the pointer handed to the RCU callback. That means the RCU callback has exclusive access to the branch, i.e. can operate as if it were holding mmu_lock for write. Furthermore, the RCU callback also doesn't need to flush TLBs because that was again done when pruning the branch.

It's the same idea that KVM already uses for root SPs, the only difference is how KVM determines that there is exactly one entity that holds a reference to the SP.

Right. This works fine for normal non-TDX case. However for TDX unfortunately the access to the removed branch (or the removed sub-page-table) isn't that "exclusive" as the SEAMCALL to truly zap that branch still needs to hold the write lock of the entire Secure EPT tree, so it can still conflict with other threads handling new faults.

This is a problem we need to deal with anyway.

Quoted 40 lines
Quoted 7 lines
Quoted 6 lines

Back to TDX, to play nice with the restriction that parents are removed only after children are removed, I believe KVM can use TDH.MEM.RANGE.BLOCK to make the parent !PRESENT. That will effectively prune the S-EPT entry and all its children, and the RCU callback will again ensure all in-flight SEAMCALLs for the children complete before KVM actually tries to zap the children.

Quoted 8 lines

Reading the spec, it seems TDH.MEM.RANGE.BLOCK only sets the Secure EPT entry which points to the entire range as "blocked", but won't go down until leaf to mark all EPT entries as "blocked", which makes sense anyway.

But it seems TDH.MEM.PAGE.REMOVE and TDH.MEM.SEPT.REMOVE both only checks whether that target EPT entry is "blocked", but doesn't check whether any parent has been marked as "blocked". Not sure whether this will be a problem. But anyway if this is a problem, we perhaps can get TDX module to fix.

Oh, I didn't mean to suggest KVM skip TDH.MEM.RANGE.BLOCK for children, I simply forgot that all S-EPT entries need to be blocked before they can be removed.

Quoted 11 lines
Quoted 6 lines

And if we rework zapping page tables, I suspect we can also address David's concern (and my not-yet-voiced concern) about polluting the TDP MMU code with logic that is necessary only for S-EPT (freezing SPTEs before populating them). Rather than update S-EPT _after_ the TDP MMU SPTE, do the S-EPT update first, i.e. invoke the KVM TDX hook before try_cmpxchg64() (or maybe instead of?). That way KVM TDX can freeze the to-be-installed SPTE without common TDP MMU needing to be aware of the change.

I don't quite understand how putting SEAMCALL before the try_cmpxchg64() can work. Let's say one thread is populating a mapping and another is zapping it. The populating thread makes SEAMCALL successfully but then try_cmpxchg64() fails, in this case how to proceed?

Ah, sorry, that was unclear. By "invoke the KVM TDX hook" I didn't mean "do the SEAMCALL", I meant KVM TDX could do its own manipulation of the KVM-managed SPTEs before the common/standard flow. E.g. something like:

	if (kvm_x86_ops.set_private_spte && private)
		r = static_call(kvm_x86_set_private_spte(...)
	else
		r = try_cmpxchg64(...) ? 0 : -EBUSY;
Quoted 6 lines

so that the common code doesn't need to do, or even be aware of, the freezing. Then I think we just need another hook in handle_removed_pt(), or maybe in what is currently __kvm_tdp_mmu_write_spte()?

I.e. fully replace the "write" operations in the TDP MMU instead of trying to smush S-EPT's requirements into the common path.

I have no preference but looks better to me as the benefits you mentioned above. :)

#62 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 9 lines · Kai
Quoted 8 lines · Sean Christopherson

In other words, once the PTE is zapped/blocked (branch is pruned), it's completely removed from the paging tree and no other tasks can access the branch (page table and its children). I.e. the only remaining reference to the branch is the pointer handed to the RCU callback. That means the RCU callback has exclusive access to the branch, i.e. can operate as if it were holding mmu_lock for write. Furthermore, the RCU callback also doesn't need to flush TLBs because that was again done when pruning the branch.

Quoted 8 lines
Quoted 2 lines

It's the same idea that KVM already uses for root SPs, the only difference is how KVM determines that there is exactly one entity that holds a reference to the SP.

Right. This works fine for normal non-TDX case. However for TDX unfortunately the access to the removed branch (or the removed sub-page-table) isn't that "exclusive" as the SEAMCALL to truly zap that branch still needs to hold the write lock of the entire Secure EPT tree, so it can still conflict with other threads handling new faults.

I thought TDX was smart enough to read-lock only the part of the tree that it's actually consuming, and write-lock only the part of the tree that it's actually modifying?

Hrm, but even if TDX takes a read-lock, there's still the problem of it needing to walk the upper levels, i.e. KVM needs to keep mid-level page tables reachable until they're fully removed. Blech. That should be a non-issue at this time though, as I don't think KVM will ever REMOVE a page table of a live guest. I need to look at the PROMOTE/DEMOTE flows...

#63 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 19 lines · Sean Christopherson
Quoted 9 lines · Kai
Quoted 8 lines · Sean Christopherson

In other words, once the PTE is zapped/blocked (branch is pruned), it's completely removed from the paging tree and no other tasks can access the branch (page table and its children). I.e. the only remaining reference to the branch is the pointer handed to the RCU callback. That means the RCU callback has exclusive access to the branch, i.e. can operate as if it were holding mmu_lock for write. Furthermore, the RCU callback also doesn't need to flush TLBs because that was again done when pruning the branch.

Quoted 8 lines
Quoted 2 lines

It's the same idea that KVM already uses for root SPs, the only difference is how KVM determines that there is exactly one entity that holds a reference to the SP.

Right. This works fine for normal non-TDX case. However for TDX unfortunately the access to the removed branch (or the removed sub-page-table) isn't that "exclusive" as the SEAMCALL to truly zap that branch still needs to hold the write lock of the entire Secure EPT tree, so it can still conflict with other threads handling new faults.

Quoted 3 lines

I thought TDX was smart enough to read-lock only the part of the tree that it's actually consuming, and write-lock only the part of the tree that it's actually modifying?

The spec says there's only exclusive/shared access to the "whole Secure EPT tree":

8.6. Secure EPT Concurrency

Secure EPT concurrency rules are designed to support the expected usage and yet be as simple as possible.

Host-Side (SEAMCALL) Functions:
• Functions that manage Secure EPT acquire exclusive access to the whole Secure EPT tree of the target TD.
• In specific cases where a Secure EPT entry update may collide with a concurrent update done by the guest TD, such host-side functions update the Secure EPT entry as a transaction, using atomic compare and exchange operations.
• TDH.MEM.SEPT.RD acquire shared access to the whole Secure EPT tree of the target TD to help prevent changes to the tree while they execute.
• Other functions that only read Secure EPT for GPA-to-HPA translation (e.g., TDH.MR.EXTEND) acquire shared access to the whole Secure EPT tree of the target TD to help prevent changes to the tree while they execute.

Quoted 6 lines

Hrm, but even if TDX takes a read-lock, there's still the problem of it needing to walk the upper levels, i.e. KVM needs to keep mid-level page tables reachable until they're fully removed. Blech. That should be a non-issue at this time though, as I don't think KVM will ever REMOVE a page table of a live guest. I need to look at the PROMOTE/DEMOTE flows...

In this series, if I read correctly, when slot is removed/moved, private mappings are zapped too. It's kinda buried in:

[PATCH v11 043/113] KVM: x86/tdp_mmu: Don't zap private pages for unsupported cases

(it's not easy to find -- I had to use 'git blame' in the actual repo to find the commit, sigh.)

#64 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 7 lines · Kai
Quoted 5 lines · Sean Christopherson

Hrm, but even if TDX takes a read-lock, there's still the problem of it needing to walk the upper levels, i.e. KVM needs to keep mid-level page tables reachable until they're fully removed. Blech. That should be a non-issue at this time though, as I don't think KVM will ever REMOVE a page table of a live guest. I need to look at the PROMOTE/DEMOTE flows...

Quoted 5 lines

In this series, if I read correctly, when slot is removed/moved, private mappings are zapped too. It's kinda buried in:

[PATCH v11 043/113] KVM: x86/tdp_mmu: Don't zap private pages for unsupported cases

That should be ok, only leaf SPTEs will be zapped in that case.

#65 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

In reply to Sean Christopherson
Quoted 110 lines · On Tue, 17 Jan 2023 20:56:17 +0000 Sean Christopherson <[email protected]>…
Quoted 45 lines · Zhi Wang
Quoted 32 lines · On Tue, 17 Jan 2023 15:55:53 +0000 Sean Christopherson <[email protected]>…
Quoted 31 lines · Zhi Wang
Quoted 9 lines · On Fri, 13 Jan 2023 15:16:08 +0000 > Sean Christopherson <[email protected]>…
Quoted 1 line · Zhi Wang

Better add a FIXME: here as this has to be fixed later.

No, leaking the page is all KVM can reasonably do here. An improved comment would be helpful, but no code change is required. tdx_reclaim_page() returns an error if and only if there's an unexpected, fatal error, e.g. a SEAMCALL with bad params, incorrect concurrency in KVM, a TDX Module bug, etc. Retrying at a later point is highly unlikely to be successful.

Hi:

The word "leaking" sounds like a situation left unhandled temporarily.

I checked the source code of the TDX module[1] for the possible reason to fail when reviewing this patch:

tdx-module-v1.0.01.01.zip\src\vmm_dispatcher\api_calls\tdh_phymem_page_reclaim.c tdx-module-v1.0.01.01.zip\src\vmm_dispatcher\api_calls\tdh_phymem_page_wbinvd.c

a. Invalid parameters. For example, page is not aligned, PA HKID is not zero...

For invalid parameters, a WARN_ON_ONCE() + return value is good enough as that is how kernel handles similar situations. The caller takes the responsibility.

b. Locks has been taken in TDX module. TDR page has been locked due to another SEAMCALL, another SEAMCALL is doing PAMT walk and holding PAMT lock...

Quoted 9 lines
Quoted 2 lines

This needs to be improved later either by retry or taking tdx_lock to avoid TDX module fails on this.

No, tdx_reclaim_page() already retries TDH.PHYMEM.PAGE.RECLAIM if the target page is contended (though I'd question the validity of even that), and TDH.PHYMEM.PAGE.WBINVD is performed only when reclaiming the TDR. If there's contention when reclaiming the TDR, then KVM effectively has a use-after-free bug, i.e. leaking the page is the least of our worries.

Quoted 8 lines

Hi:

Thanks for the reply. "Leaking" is the consquence of even failing in retry. I agree with this. But I was questioning if "retry" is really a correct and only solution when encountering lock contention in the TDX module as I saw that there are quite some magic numbers are going to be introduced because of "retry" and there were discussions about times of retry should be 3 or 1000 in TDX guest on hyper-V patches. It doesn't sound right.

Ah, yeah, I'm speaking only with respect to leaking pages on failure in this specific scenario.

Quoted 5 lines

Compare to an typical *kernel lock* case, an execution path can wait on a waitqueue and later will be woken up. We usually do contention-wait-and-retry and we rarely just do contention and retry X times. In TDX case, I understand that it is hard for the TDX module to provide similar solutions as an execution path can't stay long in the TDX module.

Let me preface the below comments by saying that this is the first time that I've seen the "Single-Step and Zero-Step Attacks Mitigation Mechanisms" behavior, i.e. the first time I've been made aware that the TDX Module can apparently decide to take S-EPT locks in the VM-Enter path.

Quoted 4 lines

1) We can always take tdx_lock (linux kernel lock) when calling a SEAMCALL that touch the TDX internal locks. But the downside is we might lose some concurrency.

This isn't really feasible in practice. Even if tdx_lock were made a spinlock (it's currently a mutex) so that it could it could be taken inside kvm->mmu_lock, acquiring a per-VM lock, let alone a global lock, in KVM's page fault handling path is not an option. KVM has a hard requirement these days of being able to handle multiple page faults in parallel.

Quoted 17 lines

2) As TDX module doesn't provide contention-and-wait, I guess the following approach might have been discussed when designing this "retry".

KERNEL TDX MODULE

SEAMCALL A -> PATH A: Taking locks

SEAMCALL B -> PATH B: Contention on a lock

<- Return "operand busy"

SEAMCALL B   -|
              |  <- Wait on a kernel waitqueue
SEAMCALL B  <-|

SEAMCALL A <- PATH A: Return

Quoted 8 lines
SEAMCALL A   -|
              |  <- Wake up the waitqueue
SEMACALL A  <-| 

SEAMCALL B -> PATH B: Taking the locks
...

Why not this scheme wasn't chosen?

AFAIK, I don't think a waitqueue approach as ever been discussed publicly. Intel may have considered the idea internally, but I don't recall anything being proposed publically (though it's entirely possible I just missed the discussion).

Quoted 9 lines

Anways, I don't think a waitqueue would be a good fit, at least not for S-EPT management, which AFAICT is the only scenario where KVM does the arbitrary "retry X times and hope things work". If the contention occurs due to the TDX Module taking an S-EPT lock in VM-Enter, then KVM won't get a chance to do the "Wake up the waitqueue" action until the next VM-Exit, which IIUC is well after the TDX Module drops the S-EPT lock. In other words, immediately retrying and then punting the problem further up the stack in KVM does seem to be the least awful "solution" if there's contention.

I should put both "waitqueue or spin" in the chart above to prevent misunderstanding.

I agree that S-EPT should be thought and treated differently than others as any lock change on that path can affect the parallelization and performance. There are many internal locks for protecting private data structures inside the security firmware, like locks for TDR, TDCS, TDVPS. PAMT(mostly SEPT-related). Any contention on these might result in a BUSY.

What I would like to clarify is: it would be better to have a mechanism to make sure a SEAMCALL can succeed at a certain point (or at least trying to succeed) as the magic number would work like a lottery in reality. E.g. the CPU doing retry is running at a higher freq than the CPU running the execution path which took the TDX inner locks due to different P states. The retry loop might end much earlier than expectation. Not even say when the same linux kernel binary with the same pre-determined number of retrying loop running on different CPU SKUs with different freqs.

We sometimes do retry in a driver to wait HW states ready is because the clock of a device might be fixed or predictable. But I am in doubt if retrying loop works in the case mentioned above.

It still likes a temporary workaround to me and I think we should be quite careful of it.

Quoted 13 lines

That said, I 100% agree that the arbitrary retry stuff is awful. The zero-step interaction in particular isn't acceptable.

Intel folks, encountering "TDX_OPERAND_BUSY | TDX_OPERAND_ID_SEPT" on VM-Enter needs to be treated as a KVM bug, even if it means teaching KVM to kill the VM if a vCPU is on the cusp of triggerring the "pre-determined number" of EPT faults mentioned in this snippet:

After a pre-determined number of such EPT violations occur on the same instruction, the TDX module starts tracking the GPAs that caused Secure EPT faults and fails further host VMM attempts to enter the TD VCPU unless previously faulting private GPAs are properly mapped in the Secure EPT.

Quoted 6 lines

If the "pre-determined number" is too low to avoid false positives, e.g. if it can be tripped by a vCPU spinning while a different vCPU finishes handling a fault, then either the behavior of the TDX Module needs to be revisited, or KVM needs to stall vCPUs that are approaching the threshold until all outstanding S-EPT faults have been serviced.

That sharply hits the point. Let's keep this in mind and start from here.

I also saw the AMD SEV-SNP host patches had retry times like (2 * cpu cores). But I haven't seen any explanation in the code and comment yet, like how it supposes to be able to work in reality. Would ask this question in the patch review. I start to feel this seems a common problem that needs to be sorted.

Quoted 4 lines

KVM shouldn't be spuriously zapping private S-EPT entries since the backing memory is pinned, which means the only way for a vCPU to truly get stuck faulting on a single instruction is if userspace is broken/malicious, in which case userspace gets to keep the pieces.

#66 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 4 lines · Zhi Wang

On Tue, 17 Jan 2023 20:56:17 +0000 Sean Christopherson <[email protected]> wrote:
What I would like to clarify is: it would be better to have a mechanism to make sure a SEAMCALL can succeed at a certain point (or at least trying to succeed) as the magic number would work like a lottery in reality.

Yep, you and I are in agreement on this point. Ideally, KVM would be able to guarantee success and treat TDX_OPERAND_BUSY errors as KVM bugs.

A message that is not in the archive (sent off-list or not yet received): [email protected]

#68 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 14 lines · Kai

arch/x86/kvm/vmx/tdx.c struct tdx_capabilities { · line 77

Quoted 10 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +10 -0
+static void tdx_clear_page(unsigned long page_pa)
+{
+	const void *zero_page = (const void *) __va(page_to_phys(ZERO_PAGE(0)));
+	void *page = __va(page_pa);
+	unsigned long i;
+
+	if (!static_cpu_has(X86_FEATURE_MOVDIR64B)) {
+		clear_page(page);
+		return;
+	}

There might be below issues here:

Quoted 3 lines

1) The kernel says static_cpu_has() should only be used in fast patch where each cycle is counted, otherwise use boot_cpu_has(). I don't know whether here you should use static_cpu_has().

That documentation is stale[*], go ahead and use cpu_feature_enabled().

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

Quoted 1 line

2) IIUC a CPU feature bit can be cleared by 'clearcpuid=xxx' kernel command

As you note below, using clearcpuid taints the kernel, i.e. any breakage due to clearcpuid is user error.

Quoted 24 lines

line, so looks you should use CPUID directly otherwise the MOVDIR64B below can be unintentionally skipped. In practice w/o doing MOVDIR64B is fine since KeyID 0 doesn't have integrity enabled, but for the purpose you want to achieve checking real CPUID should be better.

But maybe you don't want to do CPUID check here each time when reclaiming a page. In that case you can do CPUID during module initialization and cache whether MOVDIR64B is truly present. static_key is a good fit for this purpose too I think.

But I am also seeing below in the kernel documentation:

clearcpuid=X[,X...] [X86]
......
Note that using this option will taint your kernel. Also note that user programs calling CPUID directly or using the feature without checking anything will still see it. This just prevents it from being used by the kernel or shown in /proc/cpuinfo. Also note the kernel might malfunction if you disable some critical bits.

So the kernel is claiming using this will taint the kernel and it can even malfunction. So maybe it's OK to use static_cpu_has()/boot_cpu_has().

#69 lore raw

Re: [PATCH v11 018/113] KVM: TDX: create/destroy VM structure

Quoted 15 lines · Sean Christopherson
Quoted 14 lines · Kai

arch/x86/kvm/vmx/tdx.c struct tdx_capabilities { · line 77

Quoted 10 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +10 -0
+static void tdx_clear_page(unsigned long page_pa)
+{
+	const void *zero_page = (const void *) __va(page_to_phys(ZERO_PAGE(0)));
+	void *page = __va(page_pa);
+	unsigned long i;
+
+	if (!static_cpu_has(X86_FEATURE_MOVDIR64B)) {
+		clear_page(page);
+		return;
+	}

There might be below issues here:

Quoted 7 lines
Quoted 3 lines

1) The kernel says static_cpu_has() should only be used in fast patch where each cycle is counted, otherwise use boot_cpu_has(). I don't know whether here you should use static_cpu_has().

That documentation is stale[*], go ahead and use cpu_feature_enabled().

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

Thanks for the info :)

Quoted 5 lines
Quoted 2 lines

2) IIUC a CPU feature bit can be cleared by 'clearcpuid=xxx' kernel command

As you note below, using clearcpuid taints the kernel, i.e. any breakage due to clearcpuid is user error.

Agreed.

#70 lore raw

PATCH v11 19/113 KVM: TDX: initialize VM with TDX specific parameters

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TDX requires additional parameters for TDX VM for confidential execution to protect its confidentiality of its memory contents and its CPU state from any other software, including VMM. When creating guest TD VM before creating vcpu, the number of vcpu, TSC frequency (that is same among vcpus. and it can't be changed.) CPUIDs which is emulated by the TDX module. It means guest can trust those CPUIDs. and sha384 values for measurement.

Add new subcommand, KVM_TDX_INIT_VM, to pass parameters for TDX guest. It assigns encryption key to the TDX guest for memory encryption. TDX encrypts memory per-guest bases. It assigns device model passes per-VM parameters for the TDX guest. The maximum number of vcpus, tsc frequency (TDX guest has fised VM-wide TSC frequency. not per-vcpu. The TDX guest can not change it.), attributes (production or debug), available extended features (which is reflected into guest XCR0, IA32_XSS MSR), cpuids, sha384 measurements, and etc.

This subcommand is called before creating vcpu and KVM_SET_CPUID2, i.e. cpuids configurations aren't available yet. So CPUIDs configuration values needs to be passed in struct kvm_tdx_init_vm. It's device model responsibility to make this cpuid config for KVM_TDX_INIT_VM and KVM_SET_CPUID2.

---
 arch/x86/include/asm/tdx.h            |   3 +
 arch/x86/include/uapi/asm/kvm.h       |  31 ++++
 arch/x86/kvm/vmx/tdx.c                | 229 ++++++++++++++++++++++++--
 arch/x86/kvm/vmx/tdx.h                |  20 +++
 tools/arch/x86/include/uapi/asm/kvm.h |  33 ++++
 5 files changed, 306 insertions(+), 10 deletions(-)
arch/x86/include/asm/tdx.h +3 -0
index 2ca6e8ce1e43..d7ce2217279f 100644
@@ -105,6 +105,9 @@ 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
+
+/* -1 indicates CPUID leaf with no sub-leaves. */
+#define TDX_CPUID_NO_SUBLEAF	((u32)-1)
 struct tdx_cpuid_config {
 	u32	leaf;
 	u32	sub_leaf;
arch/x86/include/uapi/asm/kvm.h +31 -0
index 610b1de9eb2b..b8f28d86d4fd 100644
@@ -535,6 +535,7 @@ struct kvm_pmu_event_filter {
 /* Trust Domain eXtension sub-ioctl() commands. */
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
+	KVM_TDX_INIT_VM,
 
 	KVM_TDX_CMD_NR_MAX,
 };
@@ -580,4 +581,34 @@ struct kvm_tdx_capabilities {
 	struct kvm_tdx_cpuid_config cpuid_configs[0];
 };
 
+struct kvm_tdx_init_vm {
+	__u64 attributes;
+	__u64 mrconfigid[6];	/* sha384 digest */
+	__u64 mrowner[6];	/* sha384 digest */
+	__u64 mrownerconfig[6];	/* sha348 digest */
+	union {
+		/*
+		 * KVM_TDX_INIT_VM is called before vcpu creation, thus before
+		 * KVM_SET_CPUID2.  CPUID configurations needs to be passed.
+		 *
+		 * This configuration supersedes KVM_SET_CPUID{,2}.
+		 * The user space VMM, e.g. qemu, should make them consistent
+		 * with this values.
+		 * sizeof(struct kvm_cpuid_entry2) * KVM_MAX_CPUID_ENTRIES(256)
+		 * = 8KB.
+		 */
+		struct {
+			struct kvm_cpuid2 cpuid;
+			/* 8KB with KVM_MAX_CPUID_ENTRIES. */
+			struct kvm_cpuid_entry2 entries[];
+		};
+		/*
+		 * For future extensibility.
+		 * The size(struct kvm_tdx_init_vm) = 16KB.
+		 * This should be enough given sizeof(TD_PARAMS) = 1024
+		 */
+		__u64 reserved[2029];
+	};
+};
+
 #endif /* _ASM_X86_KVM_H */
arch/x86/kvm/vmx/tdx.c +219 -10

269 lines not on this page · raw message

arch/x86/kvm/vmx/tdx.h +20 -0
index e78d72cf4c3a..1b950f98242e 100644
@@ -2,6 +2,8 @@
 #ifndef __KVM_X86_TDX_H
 #define __KVM_X86_TDX_H
 
+#include "tdx_ops.h"
+
 #ifdef CONFIG_INTEL_TDX_HOST
 struct kvm_tdx {
 	struct kvm kvm;
@@ -9,7 +11,11 @@ struct kvm_tdx {
 	unsigned long tdr_pa;
 	unsigned long *tdcs_pa;
 
+	u64 attributes;
+	u64 xfam;
 	int hkid;
+
+	u64 tsc_offset;
 };
 
 struct vcpu_tdx {
@@ -36,6 +42,20 @@ static inline struct vcpu_tdx *to_tdx(struct kvm_vcpu *vcpu)
 {
 	return container_of(vcpu, struct vcpu_tdx, vcpu);
 }
+
+static __always_inline u64 td_tdcs_exec_read64(struct kvm_tdx *kvm_tdx, u32 field)
+{
+	struct tdx_module_output out;
+	u64 err;
+
+	err = tdh_mng_rd(kvm_tdx->tdr_pa, TDCS_EXEC(field), &out);
+	if (unlikely(err)) {
+		pr_err("TDH_MNG_RD[EXEC.0x%x] failed: 0x%llx\n", field, err);
+		return 0;
+	}
+	return out.r8;
+}
+
 #else
 struct kvm_tdx {
 	struct kvm kvm;
tools/arch/x86/include/uapi/asm/kvm.h +33 -0
index 04562740691b..eb800965b589 100644
@@ -530,6 +530,7 @@ struct kvm_pmu_event_filter {
 /* Trust Domain eXtension sub-ioctl() commands. */
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
+	KVM_TDX_INIT_VM,
 
 	KVM_TDX_CMD_NR_MAX,
 };
@@ -575,4 +576,36 @@ struct kvm_tdx_capabilities {
 	struct kvm_tdx_cpuid_config cpuid_configs[0];
 };
 
+struct kvm_tdx_init_vm {
+	__u64 attributes;
+	__u32 max_vcpus;
+	__u32 padding;
+	__u64 mrconfigid[6];    /* sha384 digest */
+	__u64 mrowner[6];       /* sha384 digest */
+	__u64 mrownerconfig[6]; /* sha348 digest */
+	union {
+		/*
+		 * KVM_TDX_INIT_VM is called before vcpu creation, thus before
+		 * KVM_SET_CPUID2.  CPUID configurations needs to be passed.
+		 *
+		 * This configuration supersedes KVM_SET_CPUID{,2}.
+		 * The user space VMM, e.g. qemu, should make them consistent
+		 * with this values.
+		 * sizeof(struct kvm_cpuid_entry2) * KVM_MAX_CPUID_ENTRIES(256)
+		 * = 8KB.
+		 */
+		struct {
+			struct kvm_cpuid2 cpuid;
+			/* 8KB with KVM_MAX_CPUID_ENTRIES. */
+			struct kvm_cpuid_entry2 entries[];
+		};
+		/*
+		 * For future extensibility.
+		 * The size(struct kvm_tdx_init_vm) = 16KB.
+		 * This should be enough given sizeof(TD_PARAMS) = 1024
+		 */
+		__u64 reserved[2028];
+	};
+};
+
 #endif /* _ASM_X86_KVM_H */
Signature · 1 line
2.25.1
#71 lore raw

Re: [PATCH v11 019/113] KVM: TDX: initialize VM with TDX specific parameters

Quoted 19 lines · On Thu, 12 Jan 2023 08:31:27 -0800 [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX requires additional parameters for TDX VM for confidential execution to protect its confidentiality of its memory contents and its CPU state from any other software, including VMM. When creating guest TD VM before creating vcpu, the number of vcpu, TSC frequency (that is same among vcpus. and it can't be changed.) CPUIDs which is emulated by the TDX module. It means guest can trust those CPUIDs. and sha384 values for measurement.

Add new subcommand, KVM_TDX_INIT_VM, to pass parameters for TDX guest. It assigns encryption key to the TDX guest for memory encryption. TDX encrypts memory per-guest bases. It assigns device model passes per-VM parameters for the TDX guest. The maximum number of vcpus, tsc frequency (TDX guest has fised VM-wide TSC frequency. not per-vcpu. The TDX guest can not change it.), attributes (production or debug), available extended features (which is reflected into guest XCR0, IA32_XSS MSR), cpuids, sha384 measurements, and etc.

Quoted 6 lines

This subcommand is called before creating vcpu and KVM_SET_CPUID2, i.e. cpuids configurations aren't available yet. So CPUIDs configuration values needs to be passed in struct kvm_tdx_init_vm. It's device model responsibility to make this cpuid config for KVM_TDX_INIT_VM and KVM_SET_CPUID2.

I find this one is take much more time to review than others. Mostly what I noticed are:

It takes quite so much time to switch between the code and the TDX module spec when verifying the configuration of TD params structs. I would suggest to organize TD params structs setup part better:

a. Describe the steps, like step 1: xxx and step 2:xxx. Make it more informative and purpose-oriented if necessary.

b. Separate them into small inline functions and add comments there. So the setup_td_params() will look much cleaner to read and follow. For example:

+ max_pa = 36;
+ entry = tdx_find_cpuid_entry(cpuid, 0x80000008, 0);
+ if (entry)
+ max_pa = entry->eax & 0xff;
+
+ td_params->eptp_controls = VMX_EPTP_MT_WB;
+ /*
+ * No CPU supports 4-level && max_pa > 48.
+ * "5-level paging and 5-level EPT" section 4.1 4-level EPT
+ * "4-level EPT is limited to translating 48-bit guest-physical
+ * addresses."
+ * cpu_has_vmx_ept_5levels() check is just in case.
+ */
+ if (cpu_has_vmx_ept_5levels() && max_pa > 48) {
+ td_params->eptp_controls |= VMX_EPTP_PWL_5;
+ td_params->exec_controls |= TDX_EXEC_CONTROL_MAX_GPAW;
+ } else {
+ td_params->eptp_controls |= VMX_EPTP_PWL_4;
+ }
+

The above snippet can be wrapped in a function as setup_eptp_controls() and then explain the purpose in the comments inside that function. Also, it would be easier to define the macros for magic numbers inside those functions.

c. Organize the CPUID virtualization in dedicated functions and brief the details there. The detail of CPUID virtualization seems super huge in the TDX module ABI spec (it took me quite some time to go through it). It would be better to have a brief to help people understand it without going through the related details.

Quoted 472 lines
---
 arch/x86/include/asm/tdx.h            |   3 +
 arch/x86/include/uapi/asm/kvm.h       |  31 ++++
 arch/x86/kvm/vmx/tdx.c                | 229 ++++++++++++++++++++++++--
 arch/x86/kvm/vmx/tdx.h                |  20 +++
 tools/arch/x86/include/uapi/asm/kvm.h |  33 ++++
 5 files changed, 306 insertions(+), 10 deletions(-)
arch/x86/include/asm/tdx.h +0 -0
index 2ca6e8ce1e43..d7ce2217279f 100644
@@ -105,6 +105,9 @@ 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

arch/x86/include/asm/tdx.h +3 -0
+
+/* -1 indicates CPUID leaf with no sub-leaves. */
+#define TDX_CPUID_NO_SUBLEAF	((u32)-1)
 struct tdx_cpuid_config {
 	u32	leaf;
 	u32	sub_leaf;
(quoted diff) +0 -0

b/arch/x86/include/uapi/asm/kvm.h index 610b1de9eb2b..b8f28d86d4fd 100644

arch/x86/include/uapi/asm/kvm.h +9 -0
@@ -535,6 +535,7 @@ struct kvm_pmu_event_filter {
 /* Trust Domain eXtension sub-ioctl() commands. */
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
+	KVM_TDX_INIT_VM,
 
 	KVM_TDX_CMD_NR_MAX,
 };
@@ -580,4 +581,34 @@ struct kvm_tdx_capabilities {
 	struct kvm_tdx_cpuid_config cpuid_configs[0];
 };
 
+struct kvm_tdx_init_vm {
+	__u64 attributes;
+	__u64 mrconfigid[6];	/* sha384 digest */
+	__u64 mrowner[6];	/* sha384 digest */
+	__u64 mrownerconfig[6];	/* sha348 digest */
+	union {
+		/*
+		 * KVM_TDX_INIT_VM is called before vcpu creation, thus

before

arch/x86/include/uapi/asm/kvm.h +1 -0
+		 * KVM_SET_CPUID2.  CPUID configurations needs to be

passed.

arch/x86/include/uapi/asm/kvm.h +3 -0
+		 *
+		 * This configuration supersedes KVM_SET_CPUID{,2}.
+		 * The user space VMM, e.g. qemu, should make them

consistent

arch/x86/include/uapi/asm/kvm.h +2 -0
+		 * with this values.
+		 * sizeof(struct kvm_cpuid_entry2) *

KVM_MAX_CPUID_ENTRIES(256)

arch/x86/include/uapi/asm/kvm.h +16 -0
+		 * = 8KB.
+		 */
+		struct {
+			struct kvm_cpuid2 cpuid;
+			/* 8KB with KVM_MAX_CPUID_ENTRIES. */
+			struct kvm_cpuid_entry2 entries[];
+		};
+		/*
+		 * For future extensibility.
+		 * The size(struct kvm_tdx_init_vm) = 16KB.
+		 * This should be enough given sizeof(TD_PARAMS) = 1024
+		 */
+		__u64 reserved[2029];
+	};
+};
+
 #endif /* _ASM_X86_KVM_H */
arch/x86/kvm/vmx/tdx.c +2 -5
index d11950d18226..0b309bbfe4e5 100644
@@ -6,7 +6,6 @@
 #include "capabilities.h"
 #include "x86_ops.h"
 #include "tdx.h"
-#include "tdx_ops.h"
 #include "x86.h"
 
 #undef pr_fmt
@@ -269,12 +268,15 @@ static int tdx_do_tdh_mng_key_config(void *param)
 	return 0;
 }
 
-static int __tdx_td_init(struct kvm *kvm);
-
 int tdx_vm_init(struct kvm *kvm)
 {
-	/* Place holder for now. */
-	return __tdx_td_init(kvm);
+	/*
+	 * This function initializes only KVM software construct.  It

doesn't

arch/x86/kvm/vmx/tdx.c +9 -1
+	 * initialize TDX stuff, e.g. TDCS, TDR, TDCX, HKID etc.
+	 * It is handled by KVM_TDX_INIT_VM, __tdx_td_init().
+	 */
+
+	return 0;
 }
 
 int tdx_dev_ioctl(void __user *argp)
@@ -323,9 +325,147 @@ int tdx_dev_ioctl(void __user *argp)
 	return 0;
 }
 
-static int __tdx_td_init(struct kvm *kvm)
+/*
+ * cpuid entry lookup in TDX cpuid config way.
+ * The difference is how to specify index(subleaves).
+ * Specify index to TDX_CPUID_NO_SUBLEAF for CPUID leaf with

no-subleaves.
+ */
+static const struct kvm_cpuid_entry2 *tdx_find_cpuid_entry(const struct kvm_cpuid2 *cpuid,

arch/x86/kvm/vmx/tdx.c +1 -0
+							   u32

function, u32 index) +{

arch/x86/kvm/vmx/tdx.c +34 -0
+	int i;
+
+	/* In TDX CPU CONFIG, TDX_CPUID_NO_SUBLEAF means index = 0. */
+	if (index == TDX_CPUID_NO_SUBLEAF)
+		index = 0;
+
+	for (i = 0; i < cpuid->nent; i++) {
+		const struct kvm_cpuid_entry2 *e = &cpuid->entries[i];
+
+		if (e->function == function &&
+		    (e->index == index ||
+		     !(e->flags & KVM_CPUID_FLAG_SIGNIFCANT_INDEX)))
+			return e;
+	}
+	return NULL;
+}
+
+static int setup_tdparams(struct kvm *kvm, struct td_params *td_params,
+			struct kvm_tdx_init_vm *init_vm)
+{
+	const struct kvm_cpuid2 *cpuid = &init_vm->cpuid;
+	const struct kvm_cpuid_entry2 *entry;
+	u64 guest_supported_xcr0;
+	u64 guest_supported_xss;
+	int max_pa;
+	int i;
+
+	if (kvm->created_vcpus)
+		return -EBUSY;
+	td_params->max_vcpus = kvm->max_vcpus;
+	td_params->attributes = init_vm->attributes;
+	if (td_params->attributes & TDX_TD_ATTRIBUTE_PERFMON) {
+		/*
+		 * TODO: save/restore PMU related registers around

TDENTER.

arch/x86/kvm/vmx/tdx.c +3 -0
+		 * Once it's done, remove this guard.
+		 */
+		pr_warn("TD doesn't support perfmon yet. KVM needs to

save/restore "

arch/x86/kvm/vmx/tdx.c +6 -0
+			"host perf registers properly.\n");
+		return -EOPNOTSUPP;
+	}
+
+	for (i = 0; i < tdx_caps.nr_cpuid_configs; i++) {
+		const struct tdx_cpuid_config *config =

&tdx_caps.cpuid_configs[i];

arch/x86/kvm/vmx/tdx.c +2 -0
+		const struct kvm_cpuid_entry2 *entry =
+			tdx_find_cpuid_entry(cpuid, config->leaf,

config->sub_leaf);

arch/x86/kvm/vmx/tdx.c +1 -0
+		struct tdx_cpuid_value *value =

&td_params->cpuid_values[i]; +

arch/x86/kvm/vmx/tdx.c +33 -0
+		if (!entry)
+			continue;
+
+		value->eax = entry->eax & config->eax;
+		value->ebx = entry->ebx & config->ebx;
+		value->ecx = entry->ecx & config->ecx;
+		value->edx = entry->edx & config->edx;
+	}
+
+	max_pa = 36;
+	entry = tdx_find_cpuid_entry(cpuid, 0x80000008, 0);
+	if (entry)
+		max_pa = entry->eax & 0xff;
+
+	td_params->eptp_controls = VMX_EPTP_MT_WB;
+	/*
+	 * No CPU supports 4-level && max_pa > 48.
+	 * "5-level paging and 5-level EPT" section 4.1 4-level EPT
+	 * "4-level EPT is limited to translating 48-bit guest-physical
+	 *  addresses."
+	 * cpu_has_vmx_ept_5levels() check is just in case.
+	 */
+	if (cpu_has_vmx_ept_5levels() && max_pa > 48) {
+		td_params->eptp_controls |= VMX_EPTP_PWL_5;
+		td_params->exec_controls |= TDX_EXEC_CONTROL_MAX_GPAW;
+	} else {
+		td_params->eptp_controls |= VMX_EPTP_PWL_4;
+	}
+
+	/* Setup td_params.xfam */
+	entry = tdx_find_cpuid_entry(cpuid, 0xd, 0);
+	if (entry)
+		guest_supported_xcr0 = (entry->eax | ((u64)entry->edx

<< 32));

arch/x86/kvm/vmx/tdx.c +7 -0
+	else
+		guest_supported_xcr0 = 0;
+	guest_supported_xcr0 &= kvm_caps.supported_xcr0;
+
+	entry = tdx_find_cpuid_entry(cpuid, 0xd, 1);
+	if (entry)
+		guest_supported_xss = (entry->ecx | ((u64)entry->edx <<

32));

arch/x86/kvm/vmx/tdx.c +3 -0
+	else
+		guest_supported_xss = 0;
+	/* PT can be exposed to TD guest regardless of KVM's XSS

support */

arch/x86/kvm/vmx/tdx.c +1 -0
+	guest_supported_xss &= (kvm_caps.supported_xss |

XFEATURE_MASK_PT); +

arch/x86/kvm/vmx/tdx.c +7 -0
+	td_params->xfam = guest_supported_xcr0 | guest_supported_xss;
+	if (td_params->xfam & XFEATURE_MASK_LBR) {
+		/*
+		 * TODO: once KVM supports LBR(save/restore LBR related
+		 * registers around TDENTER), remove this guard.
+		 */
+		pr_warn("TD doesn't support LBR yet. KVM needs to

save/restore "

arch/x86/kvm/vmx/tdx.c +10 -0
+			"IA32_LBR_DEPTH properly.\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (td_params->xfam & XFEATURE_MASK_XTILE) {
+		/*
+		 * TODO: once KVM supports AMX(save/restore AMX related
+		 * registers around TDENTER), remove this guard.
+		 */
+		pr_warn("TD doesn't support AMX yet. KVM needs to

save/restore "

arch/x86/kvm/vmx/tdx.c +16 -0
+			"IA32_XFD, IA32_XFD_ERR properly.\n");
+		return -EOPNOTSUPP;
+	}
+
+	td_params->tsc_frequency =
+		TDX_TSC_KHZ_TO_25MHZ(kvm->arch.default_tsc_khz);
+
+#define MEMCPY_SAME_SIZE(dst, src)				\
+	do {							\
+		BUILD_BUG_ON(sizeof(dst) != sizeof(src));	\
+		memcpy((dst), (src), sizeof(dst));		\
+	} while (0)
+
+	MEMCPY_SAME_SIZE(td_params->mrconfigid, init_vm->mrconfigid);
+	MEMCPY_SAME_SIZE(td_params->mrowner, init_vm->mrowner);
+	MEMCPY_SAME_SIZE(td_params->mrownerconfig,

init_vm->mrownerconfig); +

arch/x86/kvm/vmx/tdx.c +5 -2
+	return 0;
+}
+
+static int __tdx_td_init(struct kvm *kvm, struct td_params *td_params)
 {
 	struct kvm_tdx *kvm_tdx = to_kvm_tdx(kvm);
+	struct tdx_module_output out;
 	cpumask_var_t packages;
 	unsigned long *tdcs_pa = NULL;
 	unsigned long tdr_pa = 0;
@@ -439,10 +579,13 @@ static int __tdx_td_init(struct kvm *kvm)
 		}
 	}
 
-	/*
-	 * Note, TDH_MNG_INIT cannot be invoked here.  TDH_MNG_INIT

requires a dedicated

arch/x86/kvm/vmx/tdx.c +17 -2
-	 * ioctl() to define the configure CPUID values for the TD.
-	 */
+	err = tdh_mng_init(kvm_tdx->tdr_pa, __pa(td_params), &out);
+	if (WARN_ON_ONCE(err)) {
+		pr_tdx_error(TDH_MNG_INIT, err, &out);
+		ret = -EIO;
+		goto teardown;
+	}
+
 	return 0;
 
 	/*
@@ -477,6 +620,69 @@ static int __tdx_td_init(struct kvm *kvm)
 	return ret;
 }
 
+static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd)
+{
+	struct kvm_tdx *kvm_tdx = to_kvm_tdx(kvm);
+	struct kvm_tdx_init_vm *init_vm = NULL;
+	struct td_params *td_params = NULL;
+	void *entries_end;
+	int ret;
+
+	BUILD_BUG_ON(sizeof(*init_vm) != 16 * 1024);
+	BUILD_BUG_ON((sizeof(*init_vm) - offsetof(typeof(*init_vm),

entries)) /

arch/x86/kvm/vmx/tdx.c +1 -0
+		     sizeof(init_vm->entries[0]) <

KVM_MAX_CPUID_ENTRIES);

arch/x86/kvm/vmx/tdx.c +12 -0
+	BUILD_BUG_ON(sizeof(struct td_params) != 1024);
+
+	if (is_hkid_assigned(kvm_tdx))
+		return -EINVAL;
+
+	if (cmd->flags)
+		return -EINVAL;
+
+	init_vm = kzalloc(sizeof(*init_vm), GFP_KERNEL);
+	if (!init_vm)
+		return -ENOMEM;
+	if (copy_from_user(init_vm, (void __user *)cmd->data,

sizeof(*init_vm))) {

arch/x86/kvm/vmx/tdx.c +13 -0
+		ret = -EFAULT;
+		goto out;
+	}
+
+	ret = -EINVAL;
+	if (init_vm->cpuid.padding)
+		goto out;
+	/* init_vm->entries shouldn't overrun. */
+	entries_end = init_vm->entries + init_vm->cpuid.nent;
+	if (entries_end > (void *)(init_vm + 1))
+		goto out;
+	/* Unused part must be zero. */
+	if (memchr_inv(entries_end, 0, (void *)(init_vm + 1) -

entries_end))

arch/x86/kvm/vmx/tdx.c +17 -0
+		goto out;
+
+	td_params = kzalloc(sizeof(struct td_params), GFP_KERNEL);
+	if (!td_params) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = setup_tdparams(kvm, td_params, init_vm);
+	if (ret)
+		goto out;
+
+	ret = __tdx_td_init(kvm, td_params);
+	if (ret)
+		goto out;
+
+	kvm_tdx->tsc_offset = td_tdcs_exec_read64(kvm_tdx,

TD_TDCS_EXEC_TSC_OFFSET);

arch/x86/kvm/vmx/tdx.c +13 -0
+	kvm_tdx->attributes = td_params->attributes;
+	kvm_tdx->xfam = td_params->xfam;
+
+out:
+	/* kfree() accepts NULL. */
+	kfree(init_vm);
+	kfree(td_params);
+	return ret;
+}
+
 int tdx_vm_ioctl(struct kvm *kvm, void __user *argp)
 {
 	struct kvm_tdx_cmd tdx_cmd;
@@ -490,6 +696,9 @@ int tdx_vm_ioctl(struct kvm *kvm, void __user *argp)
 	mutex_lock(&kvm->lock);
 
 	switch (tdx_cmd.id) {
+	case KVM_TDX_INIT_VM:
+		r = tdx_td_init(kvm, &tdx_cmd);
+		break;
 	default:
 		r = -EINVAL;
 		goto out;
arch/x86/kvm/vmx/tdx.h +19 -0
index e78d72cf4c3a..1b950f98242e 100644
@@ -2,6 +2,8 @@
 #ifndef __KVM_X86_TDX_H
 #define __KVM_X86_TDX_H
 
+#include "tdx_ops.h"
+
 #ifdef CONFIG_INTEL_TDX_HOST
 struct kvm_tdx {
 	struct kvm kvm;
@@ -9,7 +11,11 @@ struct kvm_tdx {
 	unsigned long tdr_pa;
 	unsigned long *tdcs_pa;
 
+	u64 attributes;
+	u64 xfam;
 	int hkid;
+
+	u64 tsc_offset;
 };
 
 struct vcpu_tdx {
@@ -36,6 +42,20 @@ static inline struct vcpu_tdx *to_tdx(struct kvm_vcpu
 *vcpu) {
 	return container_of(vcpu, struct vcpu_tdx, vcpu);
 }
+
+static __always_inline u64 td_tdcs_exec_read64(struct kvm_tdx *kvm_tdx,
 u32 field) +{
+	struct tdx_module_output out;
+	u64 err;
+
+	err = tdh_mng_rd(kvm_tdx->tdr_pa, TDCS_EXEC(field), &out);
+	if (unlikely(err)) {
+		pr_err("TDH_MNG_RD[EXEC.0x%x] failed: 0x%llx\n", field,
 err);
+		return 0;
+	}
+	return out.r8;
+}
+
 #else
 struct kvm_tdx {
 	struct kvm kvm;
(quoted diff) +0 -0

b/tools/arch/x86/include/uapi/asm/kvm.h index 04562740691b..eb800965b589 100644 --- a/tools/arch/x86/include/uapi/asm/kvm.h
+++ b/tools/arch/x86/include/uapi/asm/kvm.h

arch/x86/include/uapi/asm/kvm.h +11 -0
@@ -530,6 +530,7 @@ struct kvm_pmu_event_filter {
 /* Trust Domain eXtension sub-ioctl() commands. */
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
+	KVM_TDX_INIT_VM,
 
 	KVM_TDX_CMD_NR_MAX,
 };
@@ -575,4 +576,36 @@ struct kvm_tdx_capabilities {
 	struct kvm_tdx_cpuid_config cpuid_configs[0];
 };
 
+struct kvm_tdx_init_vm {
+	__u64 attributes;
+	__u32 max_vcpus;
+	__u32 padding;
+	__u64 mrconfigid[6];    /* sha384 digest */
+	__u64 mrowner[6];       /* sha384 digest */
+	__u64 mrownerconfig[6]; /* sha348 digest */
+	union {
+		/*
+		 * KVM_TDX_INIT_VM is called before vcpu creation, thus

before

(quoted diff) +1 -0
+		 * KVM_SET_CPUID2.  CPUID configurations needs to be

passed.

arch/x86/include/uapi/asm/kvm.h +3 -0
+		 *
+		 * This configuration supersedes KVM_SET_CPUID{,2}.
+		 * The user space VMM, e.g. qemu, should make them

consistent

(quoted diff) +2 -0
+		 * with this values.
+		 * sizeof(struct kvm_cpuid_entry2) *

KVM_MAX_CPUID_ENTRIES(256)

tools/arch/x86/include/uapi/asm/kvm.h +16 -0
+		 * = 8KB.
+		 */
+		struct {
+			struct kvm_cpuid2 cpuid;
+			/* 8KB with KVM_MAX_CPUID_ENTRIES. */
+			struct kvm_cpuid_entry2 entries[];
+		};
+		/*
+		 * For future extensibility.
+		 * The size(struct kvm_tdx_init_vm) = 16KB.
+		 * This should be enough given sizeof(TD_PARAMS) = 1024
+		 */
+		__u64 reserved[2028];
+	};
+};
+
 #endif /* _ASM_X86_KVM_H */
#72 lore raw

Re: [PATCH v11 019/113] KVM: TDX: initialize VM with TDX specific parameters

In reply to [email protected]

arch/x86/include/uapi/asm/kvm.h struct kvm_tdx_capabilities { · line 594

Quoted 11 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:
arch/x86/include/uapi/asm/kvm.h +11 -0
+struct kvm_tdx_init_vm {
+	__u64 attributes;
+	__u64 mrconfigid[6];	/* sha384 digest */
+	__u64 mrowner[6];	/* sha384 digest */
+	__u64 mrownerconfig[6];	/* sha348 digest */
+	union {
+		/*
+		 * KVM_TDX_INIT_VM is called before vcpu creation, thus before
+		 * KVM_SET_CPUID2.  CPUID configurations needs to be passed.
+		 *
+		 * This configuration supersedes KVM_SET_CPUID{,2}.

What does "{,2}" mean?

Review

Quoted 2 lines
(quoted diff) +2 -0
+		 * The user space VMM, e.g. qemu, should make them consistent
+		 * with this values.

You are already using 'struct kvm_cpuid2' below. Isn't it enough to imply that userspace should organize data in the format of 'struct kvm_cpuid2'?

arch/x86/include/uapi/asm/kvm.h struct kvm_tdx_capabilities { · line 599

Quoted 3 lines
arch/x86/include/uapi/asm/kvm.h +3 -0
+		 * sizeof(struct kvm_cpuid_entry2) * KVM_MAX_CPUID_ENTRIES(256)
+		 * = 8KB.
+		 */

What does this comment try to imply?

arch/x86/include/uapi/asm/kvm.h struct kvm_tdx_capabilities { · line 603

Quoted 4 lines
arch/x86/include/uapi/asm/kvm.h +4 -0
+		struct {
+			struct kvm_cpuid2 cpuid;
+			/* 8KB with KVM_MAX_CPUID_ENTRIES. */
+			struct kvm_cpuid_entry2 entries[];

I don't understand what's the purpose of the second field?

Shouldn't the 'struct kvm_cpuid2' already have all the CPUID entries?

arch/x86/include/uapi/asm/kvm.h struct kvm_tdx_capabilities { · line 610

Quoted 7 lines
arch/x86/include/uapi/asm/kvm.h +7 -0
+		};
+		/*
+		 * For future extensibility.
+		 * The size(struct kvm_tdx_init_vm) = 16KB.
+		 * This should be enough given sizeof(TD_PARAMS) = 1024
+		 */
+		__u64 reserved[2029];

I think this is just wrong. How can you extend something after a dynamic size CPUID array?

If you want extensibility, you need to put the space before the flexible array.

Quoted 2 lines
(quoted diff) +2 -0
+	};
+};
#73 lore raw

Re: [PATCH v11 019/113] KVM: TDX: initialize VM with TDX specific parameters

Quoted 14 lines · Kai

arch/x86/include/uapi/asm/kvm.h struct kvm_tdx_capabilities { · line 594

Quoted 11 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:
arch/x86/include/uapi/asm/kvm.h +11 -0
+struct kvm_tdx_init_vm {
+	__u64 attributes;
+	__u64 mrconfigid[6];	/* sha384 digest */
+	__u64 mrowner[6];	/* sha384 digest */
+	__u64 mrownerconfig[6];	/* sha348 digest */
+	union {
+		/*
+		 * KVM_TDX_INIT_VM is called before vcpu creation, thus before
+		 * KVM_SET_CPUID2.  CPUID configurations needs to be passed.
+		 *
+		 * This configuration supersedes KVM_SET_CPUID{,2}.

What does "{,2}" mean?

Both KVM_SET_CPUID and KVM_SET_CPUID2. For comment purpose, only KVM_SET_CPUID2 suffice. I'll drop "{,2}".

Quoted 33 lines

Review

Quoted 3 lines
(quoted diff) +2 -0
+		 * The user space VMM, e.g. qemu, should make them consistent
+		 * with this values.

You are already using 'struct kvm_cpuid2' below. Isn't it enough to imply that userspace should organize data in the format of 'struct kvm_cpuid2'?

arch/x86/include/uapi/asm/kvm.h struct kvm_tdx_capabilities { · line 599

Quoted 3 lines
arch/x86/include/uapi/asm/kvm.h +3 -0
+		 * sizeof(struct kvm_cpuid_entry2) * KVM_MAX_CPUID_ENTRIES(256)
+		 * = 8KB.
+		 */

What does this comment try to imply?

arch/x86/include/uapi/asm/kvm.h struct kvm_tdx_capabilities { · line 603

Quoted 4 lines
arch/x86/include/uapi/asm/kvm.h +4 -0
+		struct {
+			struct kvm_cpuid2 cpuid;
+			/* 8KB with KVM_MAX_CPUID_ENTRIES. */
+			struct kvm_cpuid_entry2 entries[];

I don't understand what's the purpose of the second field?

Shouldn't the 'struct kvm_cpuid2' already have all the CPUID entries?

arch/x86/include/uapi/asm/kvm.h struct kvm_tdx_capabilities { · line 610

Quoted 7 lines
arch/x86/include/uapi/asm/kvm.h +7 -0
+		};
+		/*
+		 * For future extensibility.
+		 * The size(struct kvm_tdx_init_vm) = 16KB.
+		 * This should be enough given sizeof(TD_PARAMS) = 1024
+		 */
+		__u64 reserved[2029];

I think this is just wrong. How can you extend something after a dynamic size CPUID array?

Quoted 4 lines

If you want extensibility, you need to put the space before the flexible array.

Review

Quoted 2 lines
(quoted diff) +2 -0
+	};
+};

I changed the struct as follows.

struct kvm_tdx_init_vm {
        __u64 attributes;
        __u64 mrconfigid[6];    /* sha384 digest */
        __u64 mrowner[6];       /* sha384 digest */
        __u64 mrownerconfig[6]; /* sha348 digest */
        /*
         * For future extensibility to make sizeof(struct kvm_tdx_init_vm) = 8KB.
         * This should be enough given sizeof(TD_PARAMS) = 1024.
         * 8KB was chosen given because
         * sizeof(struct kvm_cpuid_entry2) * KVM_MAX_CPUID_ENTRIES(=256) = 8KB.
         */
        __u64 reserved[1004];

        /*
         * KVM_TDX_INIT_VM is called before vcpu creation, thus before
         * KVM_SET_CPUID2.
         * This configuration supersedes KVM_SET_CPUID2s for VCPUs. The user
         * space VMM, e.g. qemu, should make KVM_SET_CPUID2 consistent with this
         * values.
         */
        struct kvm_cpuid2 cpuid;
};
Signature · 1 line
Isaku Yamahata <[email protected]>
#74 lore raw

Re: [PATCH v11 019/113] KVM: TDX: initialize VM with TDX specific parameters

In reply to [email protected]

[snip]

Quoted 3 lines

+/*
+ * cpuid entry lookup in TDX cpuid config way.
+ * The difference is how to specify index(subleaves).

AFAICT you only have one caller here. If this is the only difference, will it be simpler to ask caller to simply convert TDX_CPUID_NO_SUBLEAF to 0, so this function can perhaps be removed?

arch/x86/kvm/vmx/tdx.c int tdx_dev_ioctl(void __user *argp) · line 338

Quoted 8 lines

+ * Specify index to TDX_CPUID_NO_SUBLEAF for CPUID leaf with no-subleaves.
+ */

arch/x86/kvm/vmx/tdx.c +6 -0
+static const struct kvm_cpuid_entry2 *tdx_find_cpuid_entry(const struct kvm_cpuid2 *cpuid,
+							   u32 function, u32 index)
+{
+	int i;
+
+	/* In TDX CPU CONFIG, TDX_CPUID_NO_SUBLEAF means index = 0. */
		  ^
		  CPUID_CONFIG please.
Quoted 42 lines
arch/x86/kvm/vmx/tdx.c +42 -0
+	if (index == TDX_CPUID_NO_SUBLEAF)
+		index = 0;
+
+	for (i = 0; i < cpuid->nent; i++) {
+		const struct kvm_cpuid_entry2 *e = &cpuid->entries[i];
+
+		if (e->function == function &&
+		    (e->index == index ||
+		     !(e->flags & KVM_CPUID_FLAG_SIGNIFCANT_INDEX)))
+			return e;
+	}
+	return NULL;
+}
+
+static int setup_tdparams(struct kvm *kvm, struct td_params *td_params,
+			struct kvm_tdx_init_vm *init_vm)
+{
+	const struct kvm_cpuid2 *cpuid = &init_vm->cpuid;
+	const struct kvm_cpuid_entry2 *entry;
+	u64 guest_supported_xcr0;
+	u64 guest_supported_xss;
+	int max_pa;
+	int i;
+
+	if (kvm->created_vcpus)
+		return -EBUSY;
+	td_params->max_vcpus = kvm->max_vcpus;
+	td_params->attributes = init_vm->attributes;
+	if (td_params->attributes & TDX_TD_ATTRIBUTE_PERFMON) {
+		/*
+		 * TODO: save/restore PMU related registers around TDENTER.
+		 * Once it's done, remove this guard.
+		 */
+		pr_warn("TD doesn't support perfmon yet. KVM needs to save/restore "
+			"host perf registers properly.\n");
+		return -EOPNOTSUPP;
+	}
+
+	for (i = 0; i < tdx_caps.nr_cpuid_configs; i++) {
+		const struct tdx_cpuid_config *config = &tdx_caps.cpuid_configs[i];
+		const struct kvm_cpuid_entry2 *entry =
+			tdx_find_cpuid_entry(cpuid, config->leaf, config->sub_leaf);

arch/x86/kvm/vmx/tdx.c int tdx_dev_ioctl(void __user *argp) · line 390

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		struct tdx_cpuid_value *value = &td_params->cpuid_values[i];
+
+		if (!entry)
+			continue;
+
+		value->eax = entry->eax & config->eax;
+		value->ebx = entry->ebx & config->ebx;
+		value->ecx = entry->ecx & config->ecx;
+		value->edx = entry->edx & config->edx;
+	}

A comment to explain above would be helpful, i.e TDX requires the number and the order of those entries in TD_PARAMS's cpuid_values[] must be in the same number and order with TDSYSINFO's CPUID_CONFIG.

Also, this code depends on @td_params already being zeroed. Perhaps also point it out.

[snip]

Quoted 3 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd)
+{

[snip]

Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +7 -0
+
+	ret = setup_tdparams(kvm, td_params, init_vm);
+	if (ret)
+		goto out;
+
+	ret = __tdx_td_init(kvm, td_params);
+	if (ret)

arch/x86/kvm/vmx/tdx.c static int __tdx_td_init(struct kvm *kvm) · line 682

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		goto out;
+
+	kvm_tdx->tsc_offset = td_tdcs_exec_read64(kvm_tdx, TD_TDCS_EXEC_TSC_OFFSET);
+	kvm_tdx->attributes = td_params->attributes;
+	kvm_tdx->xfam = td_params->xfam;
+
+out:
+	/* kfree() accepts NULL. */
+	kfree(init_vm);
+	kfree(td_params);

So looks KVM doesn't CPUID configurations that are passed to the TDX module.

IIUC, KVM still depends on userspace to later use KVM_SET_CPUID2 to fill the _same_ CPUID entries for each vcpu? If so, what if userspace didn't provide consistent CPUIDs in KVM_SET_CPUID2? Should we verify in KVM_SET_CPUID2 that CPUIDs are consistent?

I am thinking if some #VE handling requires CPUID to make some decision, then inconsistent CPUIDs will cause trouble, but I don't have an example now.

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +3 -0
+	return ret;
+}
+

[snip]

#75 lore raw

Re: [PATCH v11 019/113] KVM: TDX: initialize VM with TDX specific parameters

Quoted 7 lines · Kai
Quoted 3 lines

+/*
+ * cpuid entry lookup in TDX cpuid config way.
+ * The difference is how to specify index(subleaves).

AFAICT you only have one caller here. If this is the only difference, will it be simpler to ask caller to simply convert TDX_CPUID_NO_SUBLEAF to 0, so this function can perhaps be removed?

I removed the function as it turned out only one caller needs it after revise.

Quoted 43 lines
Quoted 28 lines
arch/x86/kvm/vmx/tdx.c +28 -0
+static int setup_tdparams(struct kvm *kvm, struct td_params *td_params,
+			struct kvm_tdx_init_vm *init_vm)
+{
+	const struct kvm_cpuid2 *cpuid = &init_vm->cpuid;
+	const struct kvm_cpuid_entry2 *entry;
+	u64 guest_supported_xcr0;
+	u64 guest_supported_xss;
+	int max_pa;
+	int i;
+
+	if (kvm->created_vcpus)
+		return -EBUSY;
+	td_params->max_vcpus = kvm->max_vcpus;
+	td_params->attributes = init_vm->attributes;
+	if (td_params->attributes & TDX_TD_ATTRIBUTE_PERFMON) {
+		/*
+		 * TODO: save/restore PMU related registers around TDENTER.
+		 * Once it's done, remove this guard.
+		 */
+		pr_warn("TD doesn't support perfmon yet. KVM needs to save/restore "
+			"host perf registers properly.\n");
+		return -EOPNOTSUPP;
+	}
+
+	for (i = 0; i < tdx_caps.nr_cpuid_configs; i++) {
+		const struct tdx_cpuid_config *config = &tdx_caps.cpuid_configs[i];
+		const struct kvm_cpuid_entry2 *entry =
+			tdx_find_cpuid_entry(cpuid, config->leaf, config->sub_leaf);

arch/x86/kvm/vmx/tdx.c int tdx_dev_ioctl(void __user *argp) · line 390

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		struct tdx_cpuid_value *value = &td_params->cpuid_values[i];
+
+		if (!entry)
+			continue;
+
+		value->eax = entry->eax & config->eax;
+		value->ebx = entry->ebx & config->ebx;
+		value->ecx = entry->ecx & config->ecx;
+		value->edx = entry->edx & config->edx;
+	}

A comment to explain above would be helpful, i.e TDX requires the number and the order of those entries in TD_PARAMS's cpuid_values[] must be in the same number and order with TDSYSINFO's CPUID_CONFIG.

Quoted 2 lines

Also, this code depends on @td_params already being zeroed. Perhaps also point it out.

Ok, I'll add a comment.

Quoted 27 lines

[snip]

Quoted 3 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd)
+{

[snip]

Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +7 -0
+
+	ret = setup_tdparams(kvm, td_params, init_vm);
+	if (ret)
+		goto out;
+
+	ret = __tdx_td_init(kvm, td_params);
+	if (ret)

arch/x86/kvm/vmx/tdx.c static int __tdx_td_init(struct kvm *kvm) · line 682

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		goto out;
+
+	kvm_tdx->tsc_offset = td_tdcs_exec_read64(kvm_tdx, TD_TDCS_EXEC_TSC_OFFSET);
+	kvm_tdx->attributes = td_params->attributes;
+	kvm_tdx->xfam = td_params->xfam;
+
+out:
+	/* kfree() accepts NULL. */
+	kfree(init_vm);
+	kfree(td_params);

So looks KVM doesn't CPUID configurations that are passed to the TDX module.

Quoted 4 lines

IIUC, KVM still depends on userspace to later use KVM_SET_CPUID2 to fill the _same_ CPUID entries for each vcpu? If so, what if userspace didn't provide consistent CPUIDs in KVM_SET_CPUID2? Should we verify in KVM_SET_CPUID2 that CPUIDs are consistent?

Yes. guest might be confused, but KVM is fine with it. I don't think check is necessary.

Because already user space is required to configure consistent CPUIDs (and MSRs) and KVM doesn't do such consistency check for it except minimum check for KVM to work correctly. e.g. the highest leaf number. This patch doesn't make it worse.

Quoted 2 lines

I am thinking if some #VE handling requires CPUID to make some decision, then inconsistent CPUIDs will cause trouble, but I don't have an example now.

Do you mean TDG.VP.VMCALL<CPUID>? Because guest TD doesn't VMM, #VE handler in guest should sanitize the result and should be robust against attack by returning broken value for TDG.VP.VMCALL<CPUID>.(or other TDG.VP.VMCALL)

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

PATCH v11 20/113 KVM: TDX: Make pmu_intel.c ignore guest TD case

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Because TDX KVM doesn't support PMU yet (it's future work of TDX KVM support as another patch series) and pmu_intel.c touches vmx specific structure in vcpu initialization, as workaround add dummy structure to struct vcpu_tdx and pmu_intel.c can ignore TDX case.

---
 arch/x86/kvm/vmx/pmu_intel.c | 46 +++++++++++++++++++++++++++++++++++-
 arch/x86/kvm/vmx/pmu_intel.h | 28 ++++++++++++++++++++++
 arch/x86/kvm/vmx/tdx.h       | 11 +++++++--
 arch/x86/kvm/vmx/vmx.c       |  2 +-
 arch/x86/kvm/vmx/vmx.h       | 32 +------------------------
 5 files changed, 84 insertions(+), 35 deletions(-)
 create mode 100644 arch/x86/kvm/vmx/pmu_intel.h
arch/x86/kvm/vmx/pmu_intel.c +45 -1
index efce9ad70e4e..39f43b0290c5 100644
@@ -19,6 +19,7 @@
 #include "lapic.h"
 #include "nested.h"
 #include "pmu.h"
+#include "tdx.h"
 
 #define MSR_PMC_FULL_WIDTH_BIT      (MSR_IA32_PMC0 - MSR_IA32_PERFCTR0)
 
@@ -37,6 +38,26 @@ static struct kvm_event_hw_type_mapping intel_arch_events[] = {
 /* mapping between fixed pmc index and intel_arch_events array */
 static int fixed_pmc_events[] = {1, 0, 7};
 
+struct lbr_desc *vcpu_to_lbr_desc(struct kvm_vcpu *vcpu)
+{
+#ifdef CONFIG_INTEL_TDX_HOST
+	if (is_td_vcpu(vcpu))
+		return &to_tdx(vcpu)->lbr_desc;
+#endif
+
+	return &to_vmx(vcpu)->lbr_desc;
+}
+
+struct x86_pmu_lbr *vcpu_to_lbr_records(struct kvm_vcpu *vcpu)
+{
+#ifdef CONFIG_INTEL_TDX_HOST
+	if (is_td_vcpu(vcpu))
+		return &to_tdx(vcpu)->lbr_desc.records;
+#endif
+
+	return &to_vmx(vcpu)->lbr_desc.records;
+}
+
 static void reprogram_fixed_counters(struct kvm_pmu *pmu, u64 data)
 {
 	struct kvm_pmc *pmc;
@@ -169,6 +190,23 @@ static inline struct kvm_pmc *get_fw_gp_pmc(struct kvm_pmu *pmu, u32 msr)
 	return get_gp_pmc(pmu, msr, MSR_IA32_PMC0);
 }
 
+bool intel_pmu_lbr_is_compatible(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return false;
+	return cpuid_model_is_consistent(vcpu);
+}
+
+bool intel_pmu_lbr_is_enabled(struct kvm_vcpu *vcpu)
+{
+	struct x86_pmu_lbr *lbr = vcpu_to_lbr_records(vcpu);
+
+	if (is_td_vcpu(vcpu))
+		return false;
+
+	return lbr->nr && (vcpu_get_perf_capabilities(vcpu) & PMU_CAP_LBR_FMT);
+}
+
 static bool intel_pmu_is_valid_lbr_msr(struct kvm_vcpu *vcpu, u32 index)
 {
 	struct x86_pmu_lbr *records = vcpu_to_lbr_records(vcpu);
@@ -279,6 +317,9 @@ int intel_pmu_create_guest_lbr_event(struct kvm_vcpu *vcpu)
 					PERF_SAMPLE_BRANCH_USER,
 	};
 
+	if (WARN_ON_ONCE(is_td_vcpu(vcpu)))
+		return 0;
+
 	if (unlikely(lbr_desc->event)) {
 		__set_bit(INTEL_PMC_IDX_FIXED_VLBR, pmu->pmc_in_use);
 		return 0;
@@ -588,7 +629,7 @@ static void intel_pmu_refresh(struct kvm_vcpu *vcpu)
 		INTEL_PMC_MAX_GENERIC, pmu->nr_arch_fixed_counters);
 
 	perf_capabilities = vcpu_get_perf_capabilities(vcpu);
-	if (cpuid_model_is_consistent(vcpu) &&
+	if (intel_pmu_lbr_is_compatible(vcpu) &&
 	    (perf_capabilities & PMU_CAP_LBR_FMT))
 		x86_perf_get_lbr(&lbr_desc->records);
 	else
@@ -644,6 +685,9 @@ static void intel_pmu_reset(struct kvm_vcpu *vcpu)
 	struct kvm_pmc *pmc = NULL;
 	int i;
 
+	if (is_td_vcpu(vcpu))
+		return;
+
 	for (i = 0; i < KVM_INTEL_PMC_MAX_GENERIC; i++) {
 		pmc = &pmu->gp_counters[i];
 
arch/x86/kvm/vmx/pmu_intel.h new file +28 -0
new file mode 100644
index 000000000000..66bba47c1269
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __KVM_X86_VMX_PMU_INTEL_H
+#define  __KVM_X86_VMX_PMU_INTEL_H
+
+struct lbr_desc *vcpu_to_lbr_desc(struct kvm_vcpu *vcpu);
+struct x86_pmu_lbr *vcpu_to_lbr_records(struct kvm_vcpu *vcpu);
+
+bool intel_pmu_lbr_is_compatible(struct kvm_vcpu *vcpu);
+bool intel_pmu_lbr_is_enabled(struct kvm_vcpu *vcpu);
+int intel_pmu_create_guest_lbr_event(struct kvm_vcpu *vcpu);
+
+struct lbr_desc {
+	/* Basic info about guest LBR records. */
+	struct x86_pmu_lbr records;
+
+	/*
+	 * Emulate LBR feature via passthrough LBR registers when the
+	 * per-vcpu guest LBR event is scheduled on the current pcpu.
+	 *
+	 * The records may be inaccurate if the host reclaims the LBR.
+	 */
+	struct perf_event *event;
+
+	/* True if LBRs are marked as not intercepted in the MSR bitmap */
+	bool msr_passthrough;
+};
+
+#endif /* __KVM_X86_VMX_PMU_INTEL_H */
arch/x86/kvm/vmx/tdx.h +9 -2
index 1b950f98242e..af7fdc1516d5 100644
@@ -2,9 +2,11 @@
 #ifndef __KVM_X86_TDX_H
 #define __KVM_X86_TDX_H
 
+#ifdef CONFIG_INTEL_TDX_HOST
+
+#include "pmu_intel.h"
 #include "tdx_ops.h"
 
-#ifdef CONFIG_INTEL_TDX_HOST
 struct kvm_tdx {
 	struct kvm kvm;
 
@@ -20,7 +22,12 @@ struct kvm_tdx {
 
 struct vcpu_tdx {
 	struct kvm_vcpu	vcpu;
-	/* TDX specific members follow. */
+
+	/*
+	 * Dummy to make pmu_intel not corrupt memory.
+	 * TODO: Support PMU for TDX.  Future work.
+	 */
+	struct lbr_desc lbr_desc;
 };
 
 static inline bool is_td(struct kvm *kvm)
arch/x86/kvm/vmx/vmx.c +1 -1
index 5dc7687dcf16..5b8369e67939 100644
@@ -2434,7 +2434,7 @@ int vmx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
 			if ((data & PMU_CAP_LBR_FMT) !=
 			    (kvm_caps.supported_perf_cap & PMU_CAP_LBR_FMT))
 				return 1;
-			if (!cpuid_model_is_consistent(vcpu))
+			if (!intel_pmu_lbr_is_compatible(vcpu))
 				return 1;
 		}
 		if (data & PERF_CAP_PEBS_FORMAT) {
arch/x86/kvm/vmx/vmx.h +1 -31
index a3da84f4ea45..d49d0ace9fb8 100644
@@ -11,6 +11,7 @@
 #include "capabilities.h"
 #include "../kvm_cache_regs.h"
 #include "posted_intr.h"
+#include "pmu_intel.h"
 #include "vmcs.h"
 #include "vmx_ops.h"
 #include "../cpuid.h"
@@ -105,22 +106,6 @@ static inline bool intel_pmu_has_perf_global_ctrl(struct kvm_pmu *pmu)
 	return pmu->version > 1;
 }
 
-struct lbr_desc {
-	/* Basic info about guest LBR records. */
-	struct x86_pmu_lbr records;
-
-	/*
-	 * Emulate LBR feature via passthrough LBR registers when the
-	 * per-vcpu guest LBR event is scheduled on the current pcpu.
-	 *
-	 * The records may be inaccurate if the host reclaims the LBR.
-	 */
-	struct perf_event *event;
-
-	/* True if LBRs are marked as not intercepted in the MSR bitmap */
-	bool msr_passthrough;
-};
-
 /*
  * The nested_vmx structure is part of vcpu_vmx, and holds information we need
  * for correct emulation of VMX (i.e., nested VMX) on this vcpu.
@@ -650,21 +635,6 @@ static inline struct vcpu_vmx *to_vmx(struct kvm_vcpu *vcpu)
 	return container_of(vcpu, struct vcpu_vmx, vcpu);
 }
 
-static inline struct lbr_desc *vcpu_to_lbr_desc(struct kvm_vcpu *vcpu)
-{
-	return &to_vmx(vcpu)->lbr_desc;
-}
-
-static inline struct x86_pmu_lbr *vcpu_to_lbr_records(struct kvm_vcpu *vcpu)
-{
-	return &vcpu_to_lbr_desc(vcpu)->records;
-}
-
-static inline bool intel_pmu_lbr_is_enabled(struct kvm_vcpu *vcpu)
-{
-	return !!vcpu_to_lbr_records(vcpu)->nr;
-}
-
 void intel_pmu_cross_mapped_check(struct kvm_pmu *pmu);
 int intel_pmu_create_guest_lbr_event(struct kvm_vcpu *vcpu);
 void vmx_passthrough_lbr_msrs(struct kvm_vcpu *vcpu);
Signature · 1 line
2.25.1
#77 lore raw

PATCH v11 21/113 KVM: TDX: Refuse to unplug the last cpu on the package

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

In order to reclaim TDX HKID, (i.e. when deleting guest TD), needs to call TDH.PHYMEM.PAGE.WBINVD on all packages. If we have used TDX HKID, refuse to offline the last online cpu. Add arch callback for cpu offline.

---
 arch/x86/include/asm/kvm-x86-ops.h |  1 +
 arch/x86/include/asm/kvm_host.h    |  1 +
 arch/x86/kvm/vmx/main.c            |  1 +
 arch/x86/kvm/vmx/tdx.c             | 40 +++++++++++++++++++++++++++++-
 arch/x86/kvm/vmx/x86_ops.h         |  2 ++
 arch/x86/kvm/x86.c                 |  5 ++++
 include/linux/kvm_host.h           |  1 +
 virt/kvm/kvm_main.c                | 12 +++++++--
 8 files changed, 60 insertions(+), 3 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 552de893af75..1a27f3aee982 100644
@@ -18,6 +18,7 @@ KVM_X86_OP(check_processor_compatibility)
 KVM_X86_OP(hardware_enable)
 KVM_X86_OP(hardware_disable)
 KVM_X86_OP(hardware_unsetup)
+KVM_X86_OP_OPTIONAL_RET0(offline_cpu)
 KVM_X86_OP(has_emulated_msr)
 KVM_X86_OP(vcpu_after_set_cpuid)
 KVM_X86_OP(is_vm_type_supported)
arch/x86/include/asm/kvm_host.h +1 -0
index e199ddf0bb00..30f4ddb18548 100644
@@ -1534,6 +1534,7 @@ struct kvm_x86_ops {
 	int (*hardware_enable)(void);
 	void (*hardware_disable)(void);
 	void (*hardware_unsetup)(void);
+	int (*offline_cpu)(void);
 	bool (*has_emulated_msr)(struct kvm *kvm, u32 index);
 	void (*vcpu_after_set_cpuid)(struct kvm_vcpu *vcpu);
 
arch/x86/kvm/vmx/main.c +1 -0
index c5f2515026e9..ddf0742f1f67 100644
@@ -77,6 +77,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.check_processor_compatibility = vmx_check_processor_compat,
 
 	.hardware_unsetup = vt_hardware_unsetup,
+	.offline_cpu = tdx_offline_cpu,
 
 	.hardware_enable = vmx_hardware_enable,
 	.hardware_disable = vmx_hardware_disable,
arch/x86/kvm/vmx/tdx.c +39 -1
index 0b309bbfe4e5..557a609c5147 100644
@@ -42,6 +42,7 @@ static struct tdx_capabilities tdx_caps;
  */
 static DEFINE_MUTEX(tdx_lock);
 static struct mutex *tdx_mng_key_config_lock;
+static atomic_t nr_configured_hkid;
 
 static __always_inline hpa_t set_hkid_to_hpa(hpa_t pa, u16 hkid)
 {
@@ -209,7 +210,8 @@ void tdx_mmu_release_hkid(struct kvm *kvm)
 		pr_err("tdh_mng_key_freeid failed. HKID %d is leaked.\n",
 			kvm_tdx->hkid);
 		return;
-	}
+	} else
+		atomic_dec(&nr_configured_hkid);
 
 free_hkid:
 	tdx_hkid_free(kvm_tdx);
@@ -560,6 +562,8 @@ static int __tdx_td_init(struct kvm *kvm, struct td_params *td_params)
 		if (ret)
 			break;
 	}
+	if (!ret)
+		atomic_inc(&nr_configured_hkid);
 	cpus_read_unlock();
 	free_cpumask_var(packages);
 	if (ret)
@@ -791,3 +795,37 @@ void tdx_hardware_unsetup(void)
 	/* kfree accepts NULL. */
 	kfree(tdx_mng_key_config_lock);
 }
+
+int tdx_offline_cpu(void)
+{
+	int curr_cpu = smp_processor_id();
+	cpumask_var_t packages;
+	int ret = 0;
+	int i;
+
+	if (!atomic_read(&nr_configured_hkid))
+		return 0;
+
+	/*
+	 * To reclaim hkid, need to call TDH.PHYMEM.PAGE.WBINVD on all packages.
+	 * If this is the last online cpu on the package, refuse offline.
+	 */
+	if (!zalloc_cpumask_var(&packages, GFP_KERNEL))
+		return -ENOMEM;
+
+	for_each_online_cpu(i) {
+		if (i != curr_cpu)
+			cpumask_set_cpu(topology_physical_package_id(i), packages);
+	}
+	if (!cpumask_test_cpu(topology_physical_package_id(curr_cpu), packages))
+		ret = -EBUSY;
+	free_cpumask_var(packages);
+	if (ret)
+		/*
+		 * Because it's hard for human operator to understand the
+		 * reason, warn it.
+		 */
+		pr_warn("TDX requires all packages to have an online CPU.  "
+			"Delete all TDs in order to offline all CPUs of a package.\n");
+	return ret;
+}
arch/x86/kvm/vmx/x86_ops.h +2 -0
index 3d0f519727c6..6c40dda1cc2f 100644
@@ -142,6 +142,7 @@ int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops);
 void tdx_hardware_unsetup(void);
 bool tdx_is_vm_type_supported(unsigned long type);
 int tdx_dev_ioctl(void __user *argp);
+int tdx_offline_cpu(void);
 
 int tdx_vm_init(struct kvm *kvm);
 void tdx_mmu_release_hkid(struct kvm *kvm);
@@ -152,6 +153,7 @@ static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
 static inline void tdx_hardware_unsetup(void) {}
 static inline bool tdx_is_vm_type_supported(unsigned long type) { return false; }
 static inline int tdx_dev_ioctl(void __user *argp) { return -EOPNOTSUPP; };
+static inline int tdx_offline_cpu(void) { return 0; }
 
 static inline int tdx_vm_init(struct kvm *kvm) { return -EOPNOTSUPP; }
 static inline void tdx_mmu_release_hkid(struct kvm *kvm) {}
arch/x86/kvm/x86.c +5 -0
index 0fa91a9708aa..1fb135e0c98f 100644
@@ -12100,6 +12100,11 @@ void kvm_arch_hardware_disable(void)
 	drop_user_return_notifiers();
 }
 
+int kvm_arch_offline_cpu(unsigned int cpu)
+{
+	return static_call(kvm_x86_offline_cpu)();
+}
+
 bool kvm_vcpu_is_reset_bsp(struct kvm_vcpu *vcpu)
 {
 	return vcpu->kvm->arch.bsp_vcpu_id == vcpu->vcpu_id;
include/linux/kvm_host.h +1 -0
index 6fada852c064..cd1f3634dd6a 100644
@@ -1459,6 +1459,7 @@ static inline void kvm_create_vcpu_debugfs(struct kvm_vcpu *vcpu) {}
 int kvm_arch_hardware_enable(void);
 void kvm_arch_hardware_disable(void);
 #endif
+int kvm_arch_offline_cpu(unsigned int cpu);
 int kvm_arch_vcpu_runnable(struct kvm_vcpu *vcpu);
 bool kvm_arch_vcpu_in_kernel(struct kvm_vcpu *vcpu);
 int kvm_arch_vcpu_should_kick(struct kvm_vcpu *vcpu);
virt/kvm/kvm_main.c +10 -2
index 1cfa7da92ad0..6c61b71b56d2 100644
@@ -5516,13 +5516,21 @@ static void hardware_disable_nolock(void *junk)
 	__this_cpu_write(hardware_enabled, false);
 }
 
+__weak int kvm_arch_offline_cpu(unsigned int cpu)
+{
+	return 0;
+}
+
 static int kvm_offline_cpu(unsigned int cpu)
 {
+	int r = 0;
+
 	mutex_lock(&kvm_lock);
-	if (kvm_usage_count)
+	r = kvm_arch_offline_cpu(cpu);
+	if (!r && kvm_usage_count)
 		hardware_disable_nolock(NULL);
 	mutex_unlock(&kvm_lock);
-	return 0;
+	return r;
 }
 
 static void hardware_disable_all_nolock(void)
Signature · 1 line
2.25.1
#78 lore raw

Re: [PATCH v11 021/113] KVM: TDX: Refuse to unplug the last cpu on the package

Quoted 5 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

In order to reclaim TDX HKID, (i.e. when deleting guest TD), needs to call TDH.PHYMEM.PAGE.WBINVD on all packages. If we have used TDX HKID, refuse to offline the last online cpu. Add arch callback for cpu offline.

I think it is worth to talk about suspend staff, i.e. why we only refuse to offline the last cpu when there's active TD, but not choose to offline the last cpu when TDX is enabled in KVM. People may not be able to understand immediately the reason behind this design.

Btw, I certainly don't want to speak for Sean, but it seems this was suggested by Sean? If so, add a 'Suggested-by' tag?

Quoted 4 lines

---

[snip]

arch/x86/kvm/vmx/tdx.c void tdx_hardware_unsetup(void) · line 807

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+
+int tdx_offline_cpu(void)
+{
+	int curr_cpu = smp_processor_id();
+	cpumask_var_t packages;
+	int ret = 0;
+	int i;
+
+	if (!atomic_read(&nr_configured_hkid))
+		return 0;

As mentioned above, I think it also worth to add some comment here. When people are trying to understand some code, I think mostly they are just going to look at the code itself, but won't use 'git blame' to dig out the entire changelog to understand some code.

Quoted 25 lines
arch/x86/kvm/vmx/tdx.c +24 -0
+
+	/*
+	 * To reclaim hkid, need to call TDH.PHYMEM.PAGE.WBINVD on all packages.
+	 * If this is the last online cpu on the package, refuse offline.
+	 */
+	if (!zalloc_cpumask_var(&packages, GFP_KERNEL))
+		return -ENOMEM;
+
+	for_each_online_cpu(i) {
+		if (i != curr_cpu)
+			cpumask_set_cpu(topology_physical_package_id(i), packages);
+	}
+	if (!cpumask_test_cpu(topology_physical_package_id(curr_cpu), packages))
+		ret = -EBUSY;
+	free_cpumask_var(packages);
+	if (ret)
+		/*
+		 * Because it's hard for human operator to understand the
+		 * reason, warn it.
+		 */
+		pr_warn("TDX requires all packages to have an online CPU.  "
+			"Delete all TDs in order to offline all CPUs of a package.\n");
+	return ret;
+}

[snip]

#79 lore raw

Re: [PATCH v11 021/113] KVM: TDX: Refuse to unplug the last cpu on the package

Quoted 11 lines · Kai
Quoted 5 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

In order to reclaim TDX HKID, (i.e. when deleting guest TD), needs to call TDH.PHYMEM.PAGE.WBINVD on all packages. If we have used TDX HKID, refuse to offline the last online cpu. Add arch callback for cpu offline.

I think it is worth to talk about suspend staff, i.e. why we only refuse to offline the last cpu when there's active TD, but not choose to offline the last cpu when TDX is enabled in KVM. People may not be able to understand immediately the reason behind this design.

Updated the comment.

Quoted 2 lines

Btw, I certainly don't want to speak for Sean, but it seems this was suggested by Sean? If so, add a 'Suggested-by' tag?

Added suggested-by.

Quoted 7 lines
Quoted 4 lines

---

[snip]

Quoted 15 lines

arch/x86/kvm/vmx/tdx.c void tdx_hardware_unsetup(void) · line 807

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+
+int tdx_offline_cpu(void)
+{
+	int curr_cpu = smp_processor_id();
+	cpumask_var_t packages;
+	int ret = 0;
+	int i;
+
+	if (!atomic_read(&nr_configured_hkid))
+		return 0;

As mentioned above, I think it also worth to add some comment here. When people are trying to understand some code, I think mostly they are just going to look at the code itself, but won't use 'git blame' to dig out the entire changelog to understand some code.

Makes sense. Added a comment.

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

PATCH v11 22/113 The start of TDX KVM patch series: TD vcpu creation/destruction

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

This empty commit is to mark the start of patch series of TD vcpu creation/destruction.

---
 Documentation/virt/kvm/intel-tdx-layer-status.rst | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Documentation/virt/kvm/intel-tdx-layer-status.rst +3 -3
index 221372cfb4af..a4ee04271d66 100644
@@ -9,15 +9,15 @@ Layer status
 What qemu can do
 ----------------
 - TDX VM TYPE is exposed to Qemu.
-- Qemu can try to create VM of TDX VM type and then fails.
+- Qemu can create/destroy guest of TDX vm type.
 
 Patch Layer status
 ------------------
   Patch layer                          Status
 * TDX, VMX coexistence:                 Applied
 * TDX architectural definitions:        Applied
-* TD VM creation/destruction:           Applying
-* TD vcpu creation/destruction:         Not yet
+* TD VM creation/destruction:           Applied
+* TD vcpu creation/destruction:         Applying
 * TDX EPT violation:                    Not yet
 * TD finalization:                      Not yet
 * TD vcpu enter/exit:                   Not yet
Signature · 1 line
2.25.1
#81 lore raw

PATCH v11 23/113 KVM: TDX: allocate/free TDX vcpu structure

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

The next step of TDX guest creation is to create vcpu. Allocate TDX vcpu structures, partially initialize it. Allocate pages of TDX vcpu for the TDX module. Actual donation TDX vcpu pages to the TDX module is not done yet.

In the case of the conventional case, cpuid is empty at the initialization. and cpuid is configured after the vcpu initialization. Because TDX supports only X2APIC mode, cpuid is forcibly initialized to support X2APIC on the vcpu initialization.

---
Changes v10 -> v11:
- NULL check of kvmalloc_array() in tdx_vcpu_reset. Move it to tdx_vcpu_create()

---
 arch/x86/kvm/vmx/main.c    | 40 ++++++++++++++++++--
 arch/x86/kvm/vmx/tdx.c     | 75 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 10 +++++
 arch/x86/kvm/x86.c         |  2 +
 4 files changed, 123 insertions(+), 4 deletions(-)
arch/x86/kvm/vmx/main.c +36 -4
index ddf0742f1f67..59813ca05f36 100644
@@ -63,6 +63,38 @@ static void vt_vm_free(struct kvm *kvm)
 		tdx_vm_free(kvm);
 }
 
+static int vt_vcpu_precreate(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		return 0;
+
+	return vmx_vcpu_precreate(kvm);
+}
+
+static int vt_vcpu_create(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_create(vcpu);
+
+	return vmx_vcpu_create(vcpu);
+}
+
+static void vt_vcpu_free(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_free(vcpu);
+
+	return vmx_vcpu_free(vcpu);
+}
+
+static void vt_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_reset(vcpu, init_event);
+
+	return vmx_vcpu_reset(vcpu, init_event);
+}
+
 static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 {
 	if (!is_td(kvm))
@@ -90,10 +122,10 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.vm_destroy = vt_vm_destroy,
 	.vm_free = vt_vm_free,
 
-	.vcpu_precreate = vmx_vcpu_precreate,
-	.vcpu_create = vmx_vcpu_create,
-	.vcpu_free = vmx_vcpu_free,
-	.vcpu_reset = vmx_vcpu_reset,
+	.vcpu_precreate = vt_vcpu_precreate,
+	.vcpu_create = vt_vcpu_create,
+	.vcpu_free = vt_vcpu_free,
+	.vcpu_reset = vt_vcpu_reset,
 
 	.prepare_switch_to_guest = vmx_prepare_switch_to_guest,
 	.vcpu_load = vmx_vcpu_load,
arch/x86/kvm/vmx/tdx.c +75 -0

81 lines not on this page · raw message

arch/x86/kvm/vmx/x86_ops.h +10 -0
index 6c40dda1cc2f..37ab2cfd35bc 100644
@@ -147,7 +147,12 @@ int tdx_offline_cpu(void);
 int tdx_vm_init(struct kvm *kvm);
 void tdx_mmu_release_hkid(struct kvm *kvm);
 void tdx_vm_free(struct kvm *kvm);
+
 int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
+
+int tdx_vcpu_create(struct kvm_vcpu *vcpu);
+void tdx_vcpu_free(struct kvm_vcpu *vcpu);
+void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event);
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
 static inline void tdx_hardware_unsetup(void) {}
@@ -159,7 +164,12 @@ static inline int tdx_vm_init(struct kvm *kvm) { return -EOPNOTSUPP; }
 static inline void tdx_mmu_release_hkid(struct kvm *kvm) {}
 static inline void tdx_flush_shadow_all_private(struct kvm *kvm) {}
 static inline void tdx_vm_free(struct kvm *kvm) {}
+
 static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
+
+static inline int tdx_vcpu_create(struct kvm_vcpu *vcpu) { return -EOPNOTSUPP; }
+static inline void tdx_vcpu_free(struct kvm_vcpu *vcpu) {}
+static inline void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event) {}
 #endif
 
 #endif /* __KVM_X86_VMX_X86_OPS_H */
arch/x86/kvm/x86.c +2 -0
index 1fb135e0c98f..e8bc66031a1d 100644
@@ -492,6 +492,7 @@ int kvm_set_apic_base(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
 	kvm_recalculate_apic_map(vcpu->kvm);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(kvm_set_apic_base);
 
 /*
  * Handle a fault on a hardware virtualization (VMX or SVM) instruction.
@@ -12109,6 +12110,7 @@ bool kvm_vcpu_is_reset_bsp(struct kvm_vcpu *vcpu)
 {
 	return vcpu->kvm->arch.bsp_vcpu_id == vcpu->vcpu_id;
 }
+EXPORT_SYMBOL_GPL(kvm_vcpu_is_reset_bsp);
 
 bool kvm_vcpu_is_bsp(struct kvm_vcpu *vcpu)
 {
Signature · 1 line
2.25.1
#82 lore raw

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 40 lines · On Thu, 12 Jan 2023 08:31:31 -0800 [email protected] wrote:

From: Isaku Yamahata <[email protected]>

The next step of TDX guest creation is to create vcpu. Allocate TDX vcpu structures, partially initialize it. Allocate pages of TDX vcpu for the TDX module. Actual donation TDX vcpu pages to the TDX module is not done yet.

In the case of the conventional case, cpuid is empty at the initialization. and cpuid is configured after the vcpu initialization. Because TDX supports only X2APIC mode, cpuid is forcibly initialized to support X2APIC on the vcpu initialization.

---
Changes v10 -> v11:
- NULL check of kvmalloc_array() in tdx_vcpu_reset. Move it to tdx_vcpu_create()

---
 arch/x86/kvm/vmx/main.c    | 40 ++++++++++++++++++--
 arch/x86/kvm/vmx/tdx.c     | 75 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 10 +++++
 arch/x86/kvm/x86.c         |  2 +
 4 files changed, 123 insertions(+), 4 deletions(-)
arch/x86/kvm/vmx/main.c +6 -0
index ddf0742f1f67..59813ca05f36 100644
@@ -63,6 +63,38 @@ static void vt_vm_free(struct kvm *kvm)
 		tdx_vm_free(kvm);
 }
 
+static int vt_vcpu_precreate(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		return 0;
+
+	return vmx_vcpu_precreate(kvm);

arch/x86/kvm/vmx/main.c static void vt_vm_free(struct kvm *kvm) · line 81

Quoted 10 lines
arch/x86/kvm/vmx/main.c +10 -0
+}
+
+static int vt_vcpu_create(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_create(vcpu);
+
+	return vmx_vcpu_create(vcpu);
+}
+

-----

Quoted 6 lines
arch/x86/kvm/vmx/main.c +6 -0
+static void vt_vcpu_free(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_free(vcpu);
+
+	return vmx_vcpu_free(vcpu);

arch/x86/kvm/vmx/main.c static void vt_vm_free(struct kvm *kvm) · line 97

Quoted 10 lines
arch/x86/kvm/vmx/main.c +10 -0
+}
+
+static void vt_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_reset(vcpu, init_event);
+
+	return vmx_vcpu_reset(vcpu, init_event);
+}
+

----

It seems a little strange to use return in this style. Would it be better like:

-----

if (xxx) {
	tdx_vcpu_reset(xxx);
	return; 
}

vmx_vcpu_reset(xxx);
----

?

Quoted 74 lines
arch/x86/kvm/vmx/main.c +4 -4
 static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 {
 	if (!is_td(kvm))
@@ -90,10 +122,10 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.vm_destroy = vt_vm_destroy,
 	.vm_free = vt_vm_free,
 
-	.vcpu_precreate = vmx_vcpu_precreate,
-	.vcpu_create = vmx_vcpu_create,
-	.vcpu_free = vmx_vcpu_free,
-	.vcpu_reset = vmx_vcpu_reset,
+	.vcpu_precreate = vt_vcpu_precreate,
+	.vcpu_create = vt_vcpu_create,
+	.vcpu_free = vt_vcpu_free,
+	.vcpu_reset = vt_vcpu_reset,
 
 	.prepare_switch_to_guest = vmx_prepare_switch_to_guest,
 	.vcpu_load = vmx_vcpu_load,
arch/x86/kvm/vmx/tdx.c +48 -0
index 557a609c5147..099f0737a5aa 100644
@@ -281,6 +281,81 @@ int tdx_vm_init(struct kvm *kvm)
 	return 0;
 }
 
+int tdx_vcpu_create(struct kvm_vcpu *vcpu)
+{
+	struct kvm_cpuid_entry2 *e;
+
+	/*
+	 * On cpu creation, cpuid entry is blank.  Forcibly enable
+	 * X2APIC feature to allow X2APIC.
+	 * Because vcpu_reset() can't return error, allocation is done here.
+	 */
+	WARN_ON_ONCE(vcpu->arch.cpuid_entries);
+	WARN_ON_ONCE(vcpu->arch.cpuid_nent);
+	e = kvmalloc_array(1, sizeof(*e), GFP_KERNEL_ACCOUNT);
+	if (!e)
+		return -ENOMEM;
+	*e  = (struct kvm_cpuid_entry2) {
+		.function = 1,	/* Features for X2APIC */
+		.index = 0,
+		.eax = 0,
+		.ebx = 0,
+		.ecx = 1ULL << 21,	/* X2APIC */
+		.edx = 0,
+	};
+	vcpu->arch.cpuid_entries = e;
+	vcpu->arch.cpuid_nent = 1;
+
+	/* TDX only supports x2APIC, which requires an in-kernel local APIC. */
+	if (!vcpu->arch.apic)
+		return -EINVAL;
+
+	fpstate_set_confidential(&vcpu->arch.guest_fpu);
+
+	vcpu->arch.efer = EFER_SCE | EFER_LME | EFER_LMA | EFER_NX;
+
+	vcpu->arch.cr0_guest_owned_bits = -1ul;
+	vcpu->arch.cr4_guest_owned_bits = -1ul;
+
+	vcpu->arch.tsc_offset = to_kvm_tdx(vcpu->kvm)->tsc_offset;
+	vcpu->arch.l1_tsc_offset = vcpu->arch.tsc_offset;
+	vcpu->arch.guest_state_protected =
+		!(to_kvm_tdx(vcpu->kvm)->attributes & TDX_TD_ATTRIBUTE_DEBUG);
+
+	return 0;
+}
+
+void tdx_vcpu_free(struct kvm_vcpu *vcpu)
+{
+	/* This is stub for now.  More logic will come. */
+}

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 341

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+
+void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
+{
+	struct msr_data apic_base_msr;
+
+	/* TDX doesn't support INIT event. */
+	if (WARN_ON_ONCE(init_event))
+		goto td_bugged;
+
+	/* TDX rquires X2APIC. */
                ^
               requires
Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +7 -0
+	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
+	if (kvm_vcpu_is_reset_bsp(vcpu))
+		apic_base_msr.data |= MSR_IA32_APICBASE_BSP;
+	apic_base_msr.host_initiated = true;
+	if (WARN_ON_ONCE(kvm_set_apic_base(vcpu, &apic_base_msr)))
+		goto td_bugged;
+

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 358

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	/*
+	 * Don't update mp_state to runnable because more initialization
+	 * is needed by TDX_VCPU_INIT.
+	 */
+	return;
+
+td_bugged:
+	vcpu->kvm->vm_bugged = true;
+}
+

1) Using vm_bugged to terminate the VM creation feels off. When using it in creation path, the termination still happens in xx_vcpu_run().

Thus, even something wrong happens at a certain point of the creation path, the VM creation still continues. Until the xxx_vcpu_run(), the VM termination finally happens.

Why not just fail in the creation path?

2) Move

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 345

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
+	if (kvm_vcpu_is_reset_bsp(vcpu))
+		apic_base_msr.data |= MSR_IA32_APICBASE_BSP;
+	apic_base_msr.host_initiated = true;

to:

void kvm_lapic_reset(struct kvm_vcpu *vcpu, bool init_event)

{
        struct kvm_lapic *apic = vcpu->arch.apic;
        u64 msr_val;
        int i;

        if (!init_event) {
                msr_val = APIC_DEFAULT_PHYS_BASE | MSR_IA32_APICBASE_ENABLE;

		/* here */
		if (is_td_vcpu(vcpu)) 
			msr_val = xxxx;
                if (kvm_vcpu_is_reset_bsp(vcpu))
                        msr_val |= MSR_IA32_APICBASE_BSP;
                kvm_lapic_set_base(vcpu, msr_val);
        }

PS: Is there any reason that APIC MSR in TDX doesn't need MSR_IA32_APICBASE_ENABLE?

3) Change the following:

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 340

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+	/* TDX doesn't support INIT event. */
+	if (WARN_ON_ONCE(init_event))
+		goto td_bugged;
+

to

	WARN_ON_ONCE(init_event);

kvm_cpu_deliver_init() will trigger a kvm_vcpu_reset(xxx, init_event=true), but you have already avoided this in vt_vcpu_deliver_init(). A warn is good enough to remind people.

With these changes, tdx_vcpu_reset() will only contain the CPUID configuration , using the vm_bugged to terminate the VM in tdx_vcpu_reset() can be removed.

Quoted 46 lines

int tdx_dev_ioctl(void __user *argp)
{

arch/x86/kvm/vmx/tdx.c +0 -0
 	struct kvm_tdx_capabilities __user *user_caps;
arch/x86/kvm/vmx/x86_ops.h +10 -0
index 6c40dda1cc2f..37ab2cfd35bc 100644
@@ -147,7 +147,12 @@ int tdx_offline_cpu(void);
 int tdx_vm_init(struct kvm *kvm);
 void tdx_mmu_release_hkid(struct kvm *kvm);
 void tdx_vm_free(struct kvm *kvm);
+
 int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
+
+int tdx_vcpu_create(struct kvm_vcpu *vcpu);
+void tdx_vcpu_free(struct kvm_vcpu *vcpu);
+void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event);
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
 static inline void tdx_hardware_unsetup(void) {}
@@ -159,7 +164,12 @@ static inline int tdx_vm_init(struct kvm *kvm) { return -EOPNOTSUPP; }
 static inline void tdx_mmu_release_hkid(struct kvm *kvm) {}
 static inline void tdx_flush_shadow_all_private(struct kvm *kvm) {}
 static inline void tdx_vm_free(struct kvm *kvm) {}
+
 static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
+
+static inline int tdx_vcpu_create(struct kvm_vcpu *vcpu) { return -EOPNOTSUPP; }
+static inline void tdx_vcpu_free(struct kvm_vcpu *vcpu) {}
+static inline void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event) {}
 #endif
 
 #endif /* __KVM_X86_VMX_X86_OPS_H */
arch/x86/kvm/x86.c +1 -0
index 1fb135e0c98f..e8bc66031a1d 100644
@@ -492,6 +492,7 @@ int kvm_set_apic_base(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
 	kvm_recalculate_apic_map(vcpu->kvm);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(kvm_set_apic_base);
 
 /*
  * Handle a fault on a hardware virtualization (VMX or SVM) instruction.

arch/x86/kvm/x86.c bool kvm_vcpu_is_reset_bsp(struct kvm_vcpu *vcpu) · line 12114

Quoted 5 lines
arch/x86/kvm/x86.c +1 -0
@@ -12109,6 +12110,7 @@ bool kvm_vcpu_is_reset_bsp(struct kvm_vcpu *vcpu)
 {
 	return vcpu->kvm->arch.bsp_vcpu_id == vcpu->vcpu_id;
 }
+EXPORT_SYMBOL_GPL(kvm_vcpu_is_reset_bsp);
 

The symbols don't need to be exported with the changes mentioned above.

Quoted 2 lines
arch/x86/kvm/x86.c +0 -0
 bool kvm_vcpu_is_bsp(struct kvm_vcpu *vcpu)
 {
#83 lore raw

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 81 lines · Zhi Wang
Quoted 40 lines · On Thu, 12 Jan 2023 08:31:31 -0800 [email protected] wrote:

From: Isaku Yamahata <[email protected]>

The next step of TDX guest creation is to create vcpu. Allocate TDX vcpu structures, partially initialize it. Allocate pages of TDX vcpu for the TDX module. Actual donation TDX vcpu pages to the TDX module is not done yet.

In the case of the conventional case, cpuid is empty at the initialization. and cpuid is configured after the vcpu initialization. Because TDX supports only X2APIC mode, cpuid is forcibly initialized to support X2APIC on the vcpu initialization.

---
Changes v10 -> v11:
- NULL check of kvmalloc_array() in tdx_vcpu_reset. Move it to tdx_vcpu_create()

---
 arch/x86/kvm/vmx/main.c    | 40 ++++++++++++++++++--
 arch/x86/kvm/vmx/tdx.c     | 75 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 10 +++++
 arch/x86/kvm/x86.c         |  2 +
 4 files changed, 123 insertions(+), 4 deletions(-)
arch/x86/kvm/vmx/main.c +6 -0
index ddf0742f1f67..59813ca05f36 100644
@@ -63,6 +63,38 @@ static void vt_vm_free(struct kvm *kvm)
 		tdx_vm_free(kvm);
 }
 
+static int vt_vcpu_precreate(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		return 0;
+
+	return vmx_vcpu_precreate(kvm);

arch/x86/kvm/vmx/main.c static void vt_vm_free(struct kvm *kvm) · line 81

Quoted 10 lines
arch/x86/kvm/vmx/main.c +10 -0
+}
+
+static int vt_vcpu_create(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_create(vcpu);
+
+	return vmx_vcpu_create(vcpu);
+}
+

-----

Quoted 6 lines
arch/x86/kvm/vmx/main.c +6 -0
+static void vt_vcpu_free(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_free(vcpu);
+
+	return vmx_vcpu_free(vcpu);

arch/x86/kvm/vmx/main.c static void vt_vm_free(struct kvm *kvm) · line 97

Quoted 10 lines
arch/x86/kvm/vmx/main.c +10 -0
+}
+
+static void vt_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_reset(vcpu, init_event);
+
+	return vmx_vcpu_reset(vcpu, init_event);
+}
+

----

It seems a little strange to use return in this style. Would it be better like:

-----

if (xxx) {
	tdx_vcpu_reset(xxx);
	return; 
}
Quoted 4 lines

vmx_vcpu_reset(xxx);
----

?

It's C11. I updated the code to not use the feature.

Quoted 107 lines
Quoted 74 lines
arch/x86/kvm/vmx/main.c +4 -4
 static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 {
 	if (!is_td(kvm))
@@ -90,10 +122,10 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.vm_destroy = vt_vm_destroy,
 	.vm_free = vt_vm_free,
 
-	.vcpu_precreate = vmx_vcpu_precreate,
-	.vcpu_create = vmx_vcpu_create,
-	.vcpu_free = vmx_vcpu_free,
-	.vcpu_reset = vmx_vcpu_reset,
+	.vcpu_precreate = vt_vcpu_precreate,
+	.vcpu_create = vt_vcpu_create,
+	.vcpu_free = vt_vcpu_free,
+	.vcpu_reset = vt_vcpu_reset,
 
 	.prepare_switch_to_guest = vmx_prepare_switch_to_guest,
 	.vcpu_load = vmx_vcpu_load,
arch/x86/kvm/vmx/tdx.c +48 -0
index 557a609c5147..099f0737a5aa 100644
@@ -281,6 +281,81 @@ int tdx_vm_init(struct kvm *kvm)
 	return 0;
 }
 
+int tdx_vcpu_create(struct kvm_vcpu *vcpu)
+{
+	struct kvm_cpuid_entry2 *e;
+
+	/*
+	 * On cpu creation, cpuid entry is blank.  Forcibly enable
+	 * X2APIC feature to allow X2APIC.
+	 * Because vcpu_reset() can't return error, allocation is done here.
+	 */
+	WARN_ON_ONCE(vcpu->arch.cpuid_entries);
+	WARN_ON_ONCE(vcpu->arch.cpuid_nent);
+	e = kvmalloc_array(1, sizeof(*e), GFP_KERNEL_ACCOUNT);
+	if (!e)
+		return -ENOMEM;
+	*e  = (struct kvm_cpuid_entry2) {
+		.function = 1,	/* Features for X2APIC */
+		.index = 0,
+		.eax = 0,
+		.ebx = 0,
+		.ecx = 1ULL << 21,	/* X2APIC */
+		.edx = 0,
+	};
+	vcpu->arch.cpuid_entries = e;
+	vcpu->arch.cpuid_nent = 1;
+
+	/* TDX only supports x2APIC, which requires an in-kernel local APIC. */
+	if (!vcpu->arch.apic)
+		return -EINVAL;
+
+	fpstate_set_confidential(&vcpu->arch.guest_fpu);
+
+	vcpu->arch.efer = EFER_SCE | EFER_LME | EFER_LMA | EFER_NX;
+
+	vcpu->arch.cr0_guest_owned_bits = -1ul;
+	vcpu->arch.cr4_guest_owned_bits = -1ul;
+
+	vcpu->arch.tsc_offset = to_kvm_tdx(vcpu->kvm)->tsc_offset;
+	vcpu->arch.l1_tsc_offset = vcpu->arch.tsc_offset;
+	vcpu->arch.guest_state_protected =
+		!(to_kvm_tdx(vcpu->kvm)->attributes & TDX_TD_ATTRIBUTE_DEBUG);
+
+	return 0;
+}
+
+void tdx_vcpu_free(struct kvm_vcpu *vcpu)
+{
+	/* This is stub for now.  More logic will come. */
+}

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 341

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+
+void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
+{
+	struct msr_data apic_base_msr;
+
+	/* TDX doesn't support INIT event. */
+	if (WARN_ON_ONCE(init_event))
+		goto td_bugged;
+
+	/* TDX rquires X2APIC. */
                ^
               requires
Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +7 -0
+	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
+	if (kvm_vcpu_is_reset_bsp(vcpu))
+		apic_base_msr.data |= MSR_IA32_APICBASE_BSP;
+	apic_base_msr.host_initiated = true;
+	if (WARN_ON_ONCE(kvm_set_apic_base(vcpu, &apic_base_msr)))
+		goto td_bugged;
+

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 358

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	/*
+	 * Don't update mp_state to runnable because more initialization
+	 * is needed by TDX_VCPU_INIT.
+	 */
+	return;
+
+td_bugged:
+	vcpu->kvm->vm_bugged = true;
+}
+

1) Using vm_bugged to terminate the VM creation feels off. When using it in creation path, the termination still happens in xx_vcpu_run().

Quoted 5 lines

Thus, even something wrong happens at a certain point of the creation path, the VM creation still continues. Until the xxx_vcpu_run(), the VM termination finally happens.

Why not just fail in the creation path?

I converted vm_bugged to KVM_BUG_ON. Because the td_bugged case shouldn't happen for TDX case, it's worthwhile for KVM_BUG_ON()

Quoted 10 lines

2) Move

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 345

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
+	if (kvm_vcpu_is_reset_bsp(vcpu))
+		apic_base_msr.data |= MSR_IA32_APICBASE_BSP;
+	apic_base_msr.host_initiated = true;

to:

void kvm_lapic_reset(struct kvm_vcpu *vcpu, bool init_event)

Quoted 15 lines
{
        struct kvm_lapic *apic = vcpu->arch.apic;
        u64 msr_val;
        int i;

        if (!init_event) {
                msr_val = APIC_DEFAULT_PHYS_BASE | MSR_IA32_APICBASE_ENABLE;

		/* here */
		if (is_td_vcpu(vcpu)) 
			msr_val = xxxx;
                if (kvm_vcpu_is_reset_bsp(vcpu))
                        msr_val |= MSR_IA32_APICBASE_BSP;
                kvm_lapic_set_base(vcpu, msr_val);
        }

No. Because I'm trying to contain is_td/is_td_vcpu in vmx specific and not use in common x86 code.

Quoted 2 lines

PS: Is there any reason that APIC MSR in TDX doesn't need MSR_IA32_APICBASE_ENABLE?

because LAPIC_MODE_X2APIC includes MSR_IA32_APICBASE_ENABLE. In lapic.h
LAPIC_MODE_X2APIC = MSR_IA32_APICBASE_ENABLE | X2APIC_ENABLE,

Quoted 9 lines

3) Change the following:

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 340

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+	/* TDX doesn't support INIT event. */
+	if (WARN_ON_ONCE(init_event))
+		goto td_bugged;
+

to

Quoted 5 lines
	WARN_ON_ONCE(init_event);

kvm_cpu_deliver_init() will trigger a kvm_vcpu_reset(xxx, init_event=true), but you have already avoided this in vt_vcpu_deliver_init(). A warn is good enough to remind people.

I converted it into KVM_BUG_ON().

Quoted 60 lines

With these changes, tdx_vcpu_reset() will only contain the CPUID configuration , using the vm_bugged to terminate the VM in tdx_vcpu_reset() can be removed.

Quoted 46 lines

int tdx_dev_ioctl(void __user *argp)
{

arch/x86/kvm/vmx/tdx.c +0 -0
 	struct kvm_tdx_capabilities __user *user_caps;
arch/x86/kvm/vmx/x86_ops.h +10 -0
index 6c40dda1cc2f..37ab2cfd35bc 100644
@@ -147,7 +147,12 @@ int tdx_offline_cpu(void);
 int tdx_vm_init(struct kvm *kvm);
 void tdx_mmu_release_hkid(struct kvm *kvm);
 void tdx_vm_free(struct kvm *kvm);
+
 int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
+
+int tdx_vcpu_create(struct kvm_vcpu *vcpu);
+void tdx_vcpu_free(struct kvm_vcpu *vcpu);
+void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event);
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
 static inline void tdx_hardware_unsetup(void) {}
@@ -159,7 +164,12 @@ static inline int tdx_vm_init(struct kvm *kvm) { return -EOPNOTSUPP; }
 static inline void tdx_mmu_release_hkid(struct kvm *kvm) {}
 static inline void tdx_flush_shadow_all_private(struct kvm *kvm) {}
 static inline void tdx_vm_free(struct kvm *kvm) {}
+
 static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
+
+static inline int tdx_vcpu_create(struct kvm_vcpu *vcpu) { return -EOPNOTSUPP; }
+static inline void tdx_vcpu_free(struct kvm_vcpu *vcpu) {}
+static inline void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event) {}
 #endif
 
 #endif /* __KVM_X86_VMX_X86_OPS_H */
arch/x86/kvm/x86.c +1 -0
index 1fb135e0c98f..e8bc66031a1d 100644
@@ -492,6 +492,7 @@ int kvm_set_apic_base(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
 	kvm_recalculate_apic_map(vcpu->kvm);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(kvm_set_apic_base);
 
 /*
  * Handle a fault on a hardware virtualization (VMX or SVM) instruction.

arch/x86/kvm/x86.c bool kvm_vcpu_is_reset_bsp(struct kvm_vcpu *vcpu) · line 12114

Quoted 5 lines
arch/x86/kvm/x86.c +1 -0
@@ -12109,6 +12110,7 @@ bool kvm_vcpu_is_reset_bsp(struct kvm_vcpu *vcpu)
 {
 	return vcpu->kvm->arch.bsp_vcpu_id == vcpu->vcpu_id;
 }
+EXPORT_SYMBOL_GPL(kvm_vcpu_is_reset_bsp);
 

The symbols don't need to be exported with the changes mentioned above.

Quoted 2 lines
arch/x86/kvm/x86.c +0 -0
 bool kvm_vcpu_is_bsp(struct kvm_vcpu *vcpu)
 {
Signature · 1 line
Isaku Yamahata <[email protected]>
#84 lore raw

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 235 lines · On Mon, 27 Feb 2023 15:49:14 -0800 Isaku Yamahata <[email protected]>…
Quoted 81 lines · Zhi Wang
Quoted 40 lines · On Thu, 12 Jan 2023 08:31:31 -0800 [email protected] wrote:

From: Isaku Yamahata <[email protected]>

The next step of TDX guest creation is to create vcpu. Allocate TDX vcpu structures, partially initialize it. Allocate pages of TDX vcpu for the TDX module. Actual donation TDX vcpu pages to the TDX module is not done yet.

In the case of the conventional case, cpuid is empty at the initialization. and cpuid is configured after the vcpu initialization. Because TDX supports only X2APIC mode, cpuid is forcibly initialized to support X2APIC on the vcpu initialization.

---
Changes v10 -> v11:
- NULL check of kvmalloc_array() in tdx_vcpu_reset. Move it to tdx_vcpu_create()

---
 arch/x86/kvm/vmx/main.c    | 40 ++++++++++++++++++--
 arch/x86/kvm/vmx/tdx.c     | 75 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 10 +++++
 arch/x86/kvm/x86.c         |  2 +
 4 files changed, 123 insertions(+), 4 deletions(-)
arch/x86/kvm/vmx/main.c +6 -0
index ddf0742f1f67..59813ca05f36 100644
@@ -63,6 +63,38 @@ static void vt_vm_free(struct kvm *kvm)
 		tdx_vm_free(kvm);
 }
 
+static int vt_vcpu_precreate(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		return 0;
+
+	return vmx_vcpu_precreate(kvm);

arch/x86/kvm/vmx/main.c static void vt_vm_free(struct kvm *kvm) · line 81

Quoted 10 lines
arch/x86/kvm/vmx/main.c +10 -0
+}
+
+static int vt_vcpu_create(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_create(vcpu);
+
+	return vmx_vcpu_create(vcpu);
+}
+

-----

Quoted 6 lines
arch/x86/kvm/vmx/main.c +6 -0
+static void vt_vcpu_free(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_free(vcpu);
+
+	return vmx_vcpu_free(vcpu);

arch/x86/kvm/vmx/main.c static void vt_vm_free(struct kvm *kvm) · line 97

Quoted 10 lines
arch/x86/kvm/vmx/main.c +10 -0
+}
+
+static void vt_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_reset(vcpu, init_event);
+
+	return vmx_vcpu_reset(vcpu, init_event);
+}
+

----

It seems a little strange to use return in this style. Would it be better like:

-----

if (xxx) {
	tdx_vcpu_reset(xxx);
	return; 
}
Quoted 4 lines

vmx_vcpu_reset(xxx);
----

?

It's C11. I updated the code to not use the feature.

Quoted 107 lines
Quoted 74 lines
arch/x86/kvm/vmx/main.c +4 -4
 static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 {
 	if (!is_td(kvm))
@@ -90,10 +122,10 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.vm_destroy = vt_vm_destroy,
 	.vm_free = vt_vm_free,
 
-	.vcpu_precreate = vmx_vcpu_precreate,
-	.vcpu_create = vmx_vcpu_create,
-	.vcpu_free = vmx_vcpu_free,
-	.vcpu_reset = vmx_vcpu_reset,
+	.vcpu_precreate = vt_vcpu_precreate,
+	.vcpu_create = vt_vcpu_create,
+	.vcpu_free = vt_vcpu_free,
+	.vcpu_reset = vt_vcpu_reset,
 
 	.prepare_switch_to_guest = vmx_prepare_switch_to_guest,
 	.vcpu_load = vmx_vcpu_load,
arch/x86/kvm/vmx/tdx.c +48 -0
index 557a609c5147..099f0737a5aa 100644
@@ -281,6 +281,81 @@ int tdx_vm_init(struct kvm *kvm)
 	return 0;
 }
 
+int tdx_vcpu_create(struct kvm_vcpu *vcpu)
+{
+	struct kvm_cpuid_entry2 *e;
+
+	/*
+	 * On cpu creation, cpuid entry is blank.  Forcibly enable
+	 * X2APIC feature to allow X2APIC.
+	 * Because vcpu_reset() can't return error, allocation is done here.
+	 */
+	WARN_ON_ONCE(vcpu->arch.cpuid_entries);
+	WARN_ON_ONCE(vcpu->arch.cpuid_nent);
+	e = kvmalloc_array(1, sizeof(*e), GFP_KERNEL_ACCOUNT);
+	if (!e)
+		return -ENOMEM;
+	*e  = (struct kvm_cpuid_entry2) {
+		.function = 1,	/* Features for X2APIC */
+		.index = 0,
+		.eax = 0,
+		.ebx = 0,
+		.ecx = 1ULL << 21,	/* X2APIC */
+		.edx = 0,
+	};
+	vcpu->arch.cpuid_entries = e;
+	vcpu->arch.cpuid_nent = 1;
+
+	/* TDX only supports x2APIC, which requires an in-kernel local APIC. */
+	if (!vcpu->arch.apic)
+		return -EINVAL;
+
+	fpstate_set_confidential(&vcpu->arch.guest_fpu);
+
+	vcpu->arch.efer = EFER_SCE | EFER_LME | EFER_LMA | EFER_NX;
+
+	vcpu->arch.cr0_guest_owned_bits = -1ul;
+	vcpu->arch.cr4_guest_owned_bits = -1ul;
+
+	vcpu->arch.tsc_offset = to_kvm_tdx(vcpu->kvm)->tsc_offset;
+	vcpu->arch.l1_tsc_offset = vcpu->arch.tsc_offset;
+	vcpu->arch.guest_state_protected =
+		!(to_kvm_tdx(vcpu->kvm)->attributes & TDX_TD_ATTRIBUTE_DEBUG);
+
+	return 0;
+}
+
+void tdx_vcpu_free(struct kvm_vcpu *vcpu)
+{
+	/* This is stub for now.  More logic will come. */
+}

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 341

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+
+void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
+{
+	struct msr_data apic_base_msr;
+
+	/* TDX doesn't support INIT event. */
+	if (WARN_ON_ONCE(init_event))
+		goto td_bugged;
+
+	/* TDX rquires X2APIC. */
                ^
               requires
Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +7 -0
+	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
+	if (kvm_vcpu_is_reset_bsp(vcpu))
+		apic_base_msr.data |= MSR_IA32_APICBASE_BSP;
+	apic_base_msr.host_initiated = true;
+	if (WARN_ON_ONCE(kvm_set_apic_base(vcpu, &apic_base_msr)))
+		goto td_bugged;
+

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 358

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	/*
+	 * Don't update mp_state to runnable because more initialization
+	 * is needed by TDX_VCPU_INIT.
+	 */
+	return;
+
+td_bugged:
+	vcpu->kvm->vm_bugged = true;
+}
+

1) Using vm_bugged to terminate the VM creation feels off. When using it in creation path, the termination still happens in xx_vcpu_run().

Quoted 5 lines

Thus, even something wrong happens at a certain point of the creation path, the VM creation still continues. Until the xxx_vcpu_run(), the VM termination finally happens.

Why not just fail in the creation path?

I converted vm_bugged to KVM_BUG_ON. Because the td_bugged case shouldn't happen for TDX case, it's worthwhile for KVM_BUG_ON()

Quoted 10 lines

2) Move

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 345

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
+	if (kvm_vcpu_is_reset_bsp(vcpu))
+		apic_base_msr.data |= MSR_IA32_APICBASE_BSP;
+	apic_base_msr.host_initiated = true;

to:

void kvm_lapic_reset(struct kvm_vcpu *vcpu, bool init_event)

Quoted 15 lines
{
        struct kvm_lapic *apic = vcpu->arch.apic;
        u64 msr_val;
        int i;

        if (!init_event) {
                msr_val = APIC_DEFAULT_PHYS_BASE | MSR_IA32_APICBASE_ENABLE;

		/* here */
		if (is_td_vcpu(vcpu)) 
			msr_val = xxxx;
                if (kvm_vcpu_is_reset_bsp(vcpu))
                        msr_val |= MSR_IA32_APICBASE_BSP;
                kvm_lapic_set_base(vcpu, msr_val);
        }
Quoted 3 lines

No. Because I'm trying to contain is_td/is_td_vcpu in vmx specific and not use in common x86 code.

I guess so. Centeralizing the initialization would be the nice and greatly improve the readablity of the code. Maybe adding a new callback in kvm x86_ops like .get_default_msr_val instead.

Quoted 89 lines
Quoted 3 lines

PS: Is there any reason that APIC MSR in TDX doesn't need MSR_IA32_APICBASE_ENABLE?

because LAPIC_MODE_X2APIC includes MSR_IA32_APICBASE_ENABLE. In lapic.h
LAPIC_MODE_X2APIC = MSR_IA32_APICBASE_ENABLE | X2APIC_ENABLE,

Quoted 9 lines

3) Change the following:

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 340

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+	/* TDX doesn't support INIT event. */
+	if (WARN_ON_ONCE(init_event))
+		goto td_bugged;
+

to

Quoted 5 lines
	WARN_ON_ONCE(init_event);

kvm_cpu_deliver_init() will trigger a kvm_vcpu_reset(xxx, init_event=true), but you have already avoided this in vt_vcpu_deliver_init(). A warn is good enough to remind people.

I converted it into KVM_BUG_ON().

Quoted 60 lines

With these changes, tdx_vcpu_reset() will only contain the CPUID configuration , using the vm_bugged to terminate the VM in tdx_vcpu_reset() can be removed.

Quoted 46 lines

int tdx_dev_ioctl(void __user *argp)
{

arch/x86/kvm/vmx/tdx.c +0 -0
 	struct kvm_tdx_capabilities __user *user_caps;
arch/x86/kvm/vmx/x86_ops.h +10 -0
index 6c40dda1cc2f..37ab2cfd35bc 100644
@@ -147,7 +147,12 @@ int tdx_offline_cpu(void);
 int tdx_vm_init(struct kvm *kvm);
 void tdx_mmu_release_hkid(struct kvm *kvm);
 void tdx_vm_free(struct kvm *kvm);
+
 int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
+
+int tdx_vcpu_create(struct kvm_vcpu *vcpu);
+void tdx_vcpu_free(struct kvm_vcpu *vcpu);
+void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event);
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
 static inline void tdx_hardware_unsetup(void) {}
@@ -159,7 +164,12 @@ static inline int tdx_vm_init(struct kvm *kvm) { return -EOPNOTSUPP; }
 static inline void tdx_mmu_release_hkid(struct kvm *kvm) {}
 static inline void tdx_flush_shadow_all_private(struct kvm *kvm) {}
 static inline void tdx_vm_free(struct kvm *kvm) {}
+
 static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
+
+static inline int tdx_vcpu_create(struct kvm_vcpu *vcpu) { return -EOPNOTSUPP; }
+static inline void tdx_vcpu_free(struct kvm_vcpu *vcpu) {}
+static inline void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event) {}
 #endif
 
 #endif /* __KVM_X86_VMX_X86_OPS_H */
arch/x86/kvm/x86.c +1 -0
index 1fb135e0c98f..e8bc66031a1d 100644
@@ -492,6 +492,7 @@ int kvm_set_apic_base(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
 	kvm_recalculate_apic_map(vcpu->kvm);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(kvm_set_apic_base);
 
 /*
  * Handle a fault on a hardware virtualization (VMX or SVM) instruction.

arch/x86/kvm/x86.c bool kvm_vcpu_is_reset_bsp(struct kvm_vcpu *vcpu) · line 12114

Quoted 5 lines
arch/x86/kvm/x86.c +1 -0
@@ -12109,6 +12110,7 @@ bool kvm_vcpu_is_reset_bsp(struct kvm_vcpu *vcpu)
 {
 	return vcpu->kvm->arch.bsp_vcpu_id == vcpu->vcpu_id;
 }
+EXPORT_SYMBOL_GPL(kvm_vcpu_is_reset_bsp);
 

The symbols don't need to be exported with the changes mentioned above.

Quoted 2 lines
arch/x86/kvm/x86.c +0 -0
 bool kvm_vcpu_is_bsp(struct kvm_vcpu *vcpu)
 {
#85 lore raw

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 29 lines · Zhi Wang
Quoted 26 lines · On Mon, 27 Feb 2023 15:49:14 -0800 Isaku Yamahata <[email protected]>…
Quoted 25 lines

2) Move

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 345

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
+	if (kvm_vcpu_is_reset_bsp(vcpu))
+		apic_base_msr.data |= MSR_IA32_APICBASE_BSP;
+	apic_base_msr.host_initiated = true;

to:

void kvm_lapic_reset(struct kvm_vcpu *vcpu, bool init_event)

{
        struct kvm_lapic *apic = vcpu->arch.apic;
        u64 msr_val;
        int i;

        if (!init_event) {
                msr_val = APIC_DEFAULT_PHYS_BASE | MSR_IA32_APICBASE_ENABLE;

		/* here */
		if (is_td_vcpu(vcpu)) 
			msr_val = xxxx;
                if (kvm_vcpu_is_reset_bsp(vcpu))
                        msr_val |= MSR_IA32_APICBASE_BSP;
                kvm_lapic_set_base(vcpu, msr_val);
        }
Quoted 7 lines
Quoted 3 lines

No. Because I'm trying to contain is_td/is_td_vcpu in vmx specific and not use in common x86 code.

I guess so. Centeralizing the initialization would be the nice and greatly improve the readablity of the code. Maybe adding a new callback in kvm x86_ops like .get_default_msr_val instead.

Finally I can eliminate cpuid/APIC BASE MSR, and move it user space VMM, qemu.

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

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 36 lines · On Tue, 28 Feb 2023 12:20:31 -0800 Isaku Yamahata <[email protected]>…
Quoted 33 lines · Zhi Wang
Quoted 29 lines · On Mon, 27 Feb 2023 15:49:14 -0800 Isaku Yamahata <[email protected]>…
Quoted 10 lines

2) Move

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 345

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
+	if (kvm_vcpu_is_reset_bsp(vcpu))
+		apic_base_msr.data |= MSR_IA32_APICBASE_BSP;
+	apic_base_msr.host_initiated = true;

to:

void kvm_lapic_reset(struct kvm_vcpu *vcpu, bool init_event)

Quoted 15 lines
{
        struct kvm_lapic *apic = vcpu->arch.apic;
        u64 msr_val;
        int i;

        if (!init_event) {
                msr_val = APIC_DEFAULT_PHYS_BASE | MSR_IA32_APICBASE_ENABLE;

		/* here */
		if (is_td_vcpu(vcpu)) 
			msr_val = xxxx;
                if (kvm_vcpu_is_reset_bsp(vcpu))
                        msr_val |= MSR_IA32_APICBASE_BSP;
                kvm_lapic_set_base(vcpu, msr_val);
        }

No. Because I'm trying to contain is_td/is_td_vcpu in vmx specific and not use in common x86 code.

Quoted 5 lines
Quoted 3 lines

I guess so. Centeralizing the initialization would be the nice and greatly improve the readablity of the code. Maybe adding a new callback in kvm x86_ops like .get_default_msr_val instead.

Finally I can eliminate cpuid/APIC BASE MSR, and move it user space VMM, qemu.

Great to hear.

#87 lore raw

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

In reply to [email protected]
Quoted 5 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

The next step of TDX guest creation is to create vcpu. Allocate TDX vcpu structures, partially initialize it.

Why partially initialize it? Shouldn't a better way be either: 1) not initialize at all, or; 2) fully initialize?

Can you put more _why_ here?

Quoted 3 lines

Allocate pages of TDX vcpu for the
TDX module. Actual donation TDX vcpu pages to the TDX module is not done yet.

Also, can you explain _why_ it is not done here?

Quoted 5 lines

In the case of the conventional case, cpuid is empty at the initialization. and cpuid is configured after the vcpu initialization. Because TDX supports only X2APIC mode, cpuid is forcibly initialized to support X2APIC on the vcpu initialization.

Don't quite understand here. As you said CPUID entries are configured later in KVM_SET_CPUID2, so what's the point of initializing CPUID to support x2apic here?

Are you suggesting KVM_SET_CPUID2 will be somehow rejected for TDX guest, or there will be special handling to make sure the CPUID initialized here won't be overwritten later?

Please explain clearly here.

Quoted 83 lines

---
Changes v10 -> v11:
- NULL check of kvmalloc_array() in tdx_vcpu_reset. Move it to tdx_vcpu_create()

---
 arch/x86/kvm/vmx/main.c    | 40 ++++++++++++++++++--
 arch/x86/kvm/vmx/tdx.c     | 75 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 10 +++++
 arch/x86/kvm/x86.c         |  2 +
 4 files changed, 123 insertions(+), 4 deletions(-)
arch/x86/kvm/vmx/main.c +36 -4
index ddf0742f1f67..59813ca05f36 100644
@@ -63,6 +63,38 @@ static void vt_vm_free(struct kvm *kvm)
 		tdx_vm_free(kvm);
 }
 
+static int vt_vcpu_precreate(struct kvm *kvm)
+{
+	if (is_td(kvm))
+		return 0;
+
+	return vmx_vcpu_precreate(kvm);
+}
+
+static int vt_vcpu_create(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_create(vcpu);
+
+	return vmx_vcpu_create(vcpu);
+}
+
+static void vt_vcpu_free(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_free(vcpu);
+
+	return vmx_vcpu_free(vcpu);
+}
+
+static void vt_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_vcpu_reset(vcpu, init_event);
+
+	return vmx_vcpu_reset(vcpu, init_event);
+}
+
 static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 {
 	if (!is_td(kvm))
@@ -90,10 +122,10 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.vm_destroy = vt_vm_destroy,
 	.vm_free = vt_vm_free,
 
-	.vcpu_precreate = vmx_vcpu_precreate,
-	.vcpu_create = vmx_vcpu_create,
-	.vcpu_free = vmx_vcpu_free,
-	.vcpu_reset = vmx_vcpu_reset,
+	.vcpu_precreate = vt_vcpu_precreate,
+	.vcpu_create = vt_vcpu_create,
+	.vcpu_free = vt_vcpu_free,
+	.vcpu_reset = vt_vcpu_reset,
 
 	.prepare_switch_to_guest = vmx_prepare_switch_to_guest,
 	.vcpu_load = vmx_vcpu_load,
arch/x86/kvm/vmx/tdx.c +2 -0
index 557a609c5147..099f0737a5aa 100644
@@ -281,6 +281,81 @@ int tdx_vm_init(struct kvm *kvm)
 	return 0;
 }
 
+int tdx_vcpu_create(struct kvm_vcpu *vcpu)
+{

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 295

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	struct kvm_cpuid_entry2 *e;
+
+	/*
+	 * On cpu creation, cpuid entry is blank.  Forcibly enable
+	 * X2APIC feature to allow X2APIC.
+	 * Because vcpu_reset() can't return error, allocation is done here.
+	 */
+	WARN_ON_ONCE(vcpu->arch.cpuid_entries);
+	WARN_ON_ONCE(vcpu->arch.cpuid_nent);
+	e = kvmalloc_array(1, sizeof(*e), GFP_KERNEL_ACCOUNT);

You don't need to use kvmalloc_array() when only allocating one entry.

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 307

Quoted 12 lines
arch/x86/kvm/vmx/tdx.c +12 -0
+	if (!e)
+		return -ENOMEM;
+	*e  = (struct kvm_cpuid_entry2) {
+		.function = 1,	/* Features for X2APIC */
+		.index = 0,
+		.eax = 0,
+		.ebx = 0,
+		.ecx = 1ULL << 21,	/* X2APIC */
+		.edx = 0,
+	};
+	vcpu->arch.cpuid_entries = e;
+	vcpu->arch.cpuid_nent = 1;

As mentioned above, why doing it here? Won't be this be overwritten later in KVM_SET_CPUID2?

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 311

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+
+	/* TDX only supports x2APIC, which requires an in-kernel local APIC. */
+	if (!vcpu->arch.apic)
+		return -EINVAL;

If this is hit, what happens to the CPUID entry allocated above? It's absolutely not clear here in this patch.

Quoted 18 lines
arch/x86/kvm/vmx/tdx.c +18 -0
+
+	fpstate_set_confidential(&vcpu->arch.guest_fpu);
+
+	vcpu->arch.efer = EFER_SCE | EFER_LME | EFER_LMA | EFER_NX;
+
+	vcpu->arch.cr0_guest_owned_bits = -1ul;
+	vcpu->arch.cr4_guest_owned_bits = -1ul;
+
+	vcpu->arch.tsc_offset = to_kvm_tdx(vcpu->kvm)->tsc_offset;
+	vcpu->arch.l1_tsc_offset = vcpu->arch.tsc_offset;
+	vcpu->arch.guest_state_protected =
+		!(to_kvm_tdx(vcpu->kvm)->attributes & TDX_TD_ATTRIBUTE_DEBUG);
+
+	return 0;
+}
+
+void tdx_vcpu_free(struct kvm_vcpu *vcpu)
+{

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 339

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	/* This is stub for now.  More logic will come. */
+}
+
+void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
+{
+	struct msr_data apic_base_msr;
+
+	/* TDX doesn't support INIT event. */
+	if (WARN_ON_ONCE(init_event))
+		goto td_bugged;

Should we use KVM_BUG_ON()?

Again, it appears this depends on how KVM handles INIT, which is done in a later patch far way:

[PATCH v11 102/113] KVM: TDX: Silently ignore INIT/SIPI

And there's no material explaining how it is handled in either changelog or comment, so to me it's not reviewable.

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 347

Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +8 -0
+
+	/* TDX rquires X2APIC. */
+	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
+	if (kvm_vcpu_is_reset_bsp(vcpu))
+		apic_base_msr.data |= MSR_IA32_APICBASE_BSP;
+	apic_base_msr.host_initiated = true;
+	if (WARN_ON_ONCE(kvm_set_apic_base(vcpu, &apic_base_msr)))
+		goto td_bugged;

I think we have KVM_BUG_ON()?

TDX requires a lot more staff then just x2apic, why only x2apic is done here, particularly in _this_ patch?

Quoted 12 lines
arch/x86/kvm/vmx/tdx.c +11 -0
+
+	/*
+	 * Don't update mp_state to runnable because more initialization
+	 * is needed by TDX_VCPU_INIT.
+	 */
+	return;
+
+td_bugged:
+	vcpu->kvm->vm_bugged = true;
+}
+
 

[snip]

#88 lore raw

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 25 lines · Kai
Quoted 5 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

The next step of TDX guest creation is to create vcpu. Allocate TDX vcpu structures, partially initialize it.

Why partially initialize it? Shouldn't a better way be either: 1) not initialize at all, or; 2) fully initialize?

Can you put more _why_ here?

Quoted 3 lines

Allocate pages of TDX vcpu for the
TDX module. Actual donation TDX vcpu pages to the TDX module is not done yet.

Also, can you explain _why_ it is not done here?

Quoted 5 lines

In the case of the conventional case, cpuid is empty at the initialization. and cpuid is configured after the vcpu initialization. Because TDX supports only X2APIC mode, cpuid is forcibly initialized to support X2APIC on the vcpu initialization.

Quoted 6 lines

Don't quite understand here. As you said CPUID entries are configured later in
KVM_SET_CPUID2, so what's the point of initializing CPUID to support x2api> Are you suggesting KVM_SET_CPUID2 will be somehow rejected for TDX guest, or there will be special handling to make sure the CPUID initialized here won't be
overwritten later?

Please explain clearly here.

Here is the updated one.

The next step of TDX guest creation is to create vcpu. Allocate TDX vcpu structures, initialize it that doesn't require TDX SEAMCALL. TDX specific vcpuid initialization will be implemented as independent KVM_TDX_INIT_VCPU so that when error occurs it's easy to determine which component has the issue, KVM or TDX.

In the case of the conventional case, cpuid is empty at the initialization. and cpuid is configured after the vcpu initialization. Because TDX supports only X2APIC mode, cpuid is forcibly set to support X2APIC and APIC BASE MSR is forcibly set to X2APIC mode. The MSR will be read only for guest TD. Because kvm_arch_vcpu_create() also initializes kvm MMU that depends on local apic settings. So x2apic needs to be initialized to X2APIC mode by vcpu_reset method before KVM mmu initialization.

Quoted 8 lines
Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +0 -0
index 557a609c5147..099f0737a5aa 100644
@@ -281,6 +281,81 @@ int tdx_vm_init(struct kvm *kvm)
 	return 0;
 }
 
Quoted 14 lines

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 295

Quoted 12 lines
arch/x86/kvm/vmx/tdx.c +12 -0
+int tdx_vcpu_create(struct kvm_vcpu *vcpu)
+{
+	struct kvm_cpuid_entry2 *e;
+
+	/*
+	 * On cpu creation, cpuid entry is blank.  Forcibly enable
+	 * X2APIC feature to allow X2APIC.
+	 * Because vcpu_reset() can't return error, allocation is done here.
+	 */
+	WARN_ON_ONCE(vcpu->arch.cpuid_entries);
+	WARN_ON_ONCE(vcpu->arch.cpuid_nent);
+	e = kvmalloc_array(1, sizeof(*e), GFP_KERNEL_ACCOUNT);

You don't need to use kvmalloc_array() when only allocating one entry.

I'll make it kvmalloc() and add comment on kvmalloc(), not kmalloc().

Quoted 7 lines
Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +7 -0
+	if (!e)
+		return -ENOMEM;
+	*e  = (struct kvm_cpuid_entry2) {
+		.function = 1,	/* Features for X2APIC */
+		.index = 0,
+		.eax = 0,
+		.ebx = 0,
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 307

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+		.ecx = 1ULL << 21,	/* X2APIC */
+		.edx = 0,
+	};
+	vcpu->arch.cpuid_entries = e;
+	vcpu->arch.cpuid_nent = 1;

As mentioned above, why doing it here? Won't be this be overwritten later in KVM_SET_CPUID2?

Yes, user space VMM can overwrite cpuid[0x1] and APIC base MSR. But it doesn't matter because it's a bug of user space VMM. user space VMM has to keep the consistency of cpuid and MSRs.
Because TDX module virtualizes cpuid[0x1].x2apic to fixed 1, KVM value doesn't matter after vcpu creation.
Because KVM virtualizes APIC base as read only to guest, cpuid[0x1].x2apic doesn't matter after vcpu creation as long as user space VMM keeps KVM APIC BASE value.

I'll add a comment.

Quoted 7 lines

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 311

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+
+	/* TDX only supports x2APIC, which requires an in-kernel local APIC. */
+	if (!vcpu->arch.apic)
+		return -EINVAL;

If this is hit, what happens to the CPUID entry allocated above? It's absolutely not clear here in this patch.

It's memory leak. I'll move the check before memory allocation.

Quoted 31 lines
Quoted 18 lines
arch/x86/kvm/vmx/tdx.c +18 -0
+
+	fpstate_set_confidential(&vcpu->arch.guest_fpu);
+
+	vcpu->arch.efer = EFER_SCE | EFER_LME | EFER_LMA | EFER_NX;
+
+	vcpu->arch.cr0_guest_owned_bits = -1ul;
+	vcpu->arch.cr4_guest_owned_bits = -1ul;
+
+	vcpu->arch.tsc_offset = to_kvm_tdx(vcpu->kvm)->tsc_offset;
+	vcpu->arch.l1_tsc_offset = vcpu->arch.tsc_offset;
+	vcpu->arch.guest_state_protected =
+		!(to_kvm_tdx(vcpu->kvm)->attributes & TDX_TD_ATTRIBUTE_DEBUG);
+
+	return 0;
+}
+
+void tdx_vcpu_free(struct kvm_vcpu *vcpu)
+{

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 339

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	/* This is stub for now.  More logic will come. */
+}
+
+void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
+{
+	struct msr_data apic_base_msr;
+
+	/* TDX doesn't support INIT event. */
+	if (WARN_ON_ONCE(init_event))
+		goto td_bugged;

Should we use KVM_BUG_ON()?

Quoted 7 lines

Again, it appears this depends on how KVM handles INIT, which is done in a later patch far way:

[PATCH v11 102/113] KVM: TDX: Silently ignore INIT/SIPI

And there's no material explaining how it is handled in either changelog or comment, so to me it's not reviewable.

I'll convert them to KVM_BUG_ON(). With this patch, I'll remove WARN_ON_ONCE() and add KVM_BUG_ON() with the later patch.

Quoted 11 lines

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 347

Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +8 -0
+
+	/* TDX rquires X2APIC. */
+	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
+	if (kvm_vcpu_is_reset_bsp(vcpu))
+		apic_base_msr.data |= MSR_IA32_APICBASE_BSP;
+	apic_base_msr.host_initiated = true;
+	if (WARN_ON_ONCE(kvm_set_apic_base(vcpu, &apic_base_msr)))
+		goto td_bugged;

I think we have KVM_BUG_ON()?

Quoted 2 lines

TDX requires a lot more staff then just x2apic, why only x2apic is done here, particularly in _this_ patch?

After this callback, kvm mmu initialization follows. It depends on apic setting. X2APIC is only devication from the common logic kvm_lapic_reset(). I'll add a comment.

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

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 16 lines · Isaku Yamahata
Quoted 7 lines
Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +7 -0
+	if (!e)
+		return -ENOMEM;
+	*e  = (struct kvm_cpuid_entry2) {
+		.function = 1,	/* Features for X2APIC */
+		.index = 0,
+		.eax = 0,
+		.ebx = 0,
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c int tdx_vm_init(struct kvm *kvm) · line 307

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+		.ecx = 1ULL << 21,	/* X2APIC */
+		.edx = 0,
+	};
+	vcpu->arch.cpuid_entries = e;
+	vcpu->arch.cpuid_nent = 1;

As mentioned above, why doing it here? Won't be this be overwritten later in KVM_SET_CPUID2?

Quoted 11 lines

Yes, user space VMM can overwrite cpuid[0x1] and APIC base MSR.  But it doesn't
matter because it's a bug of user space VMM. user space VMM has to keep the consistency of cpuid and MSRs.
Because TDX module virtualizes cpuid[0x1].x2apic to fixed 1, KVM value doesn't matter after vcpu creation.
Because KVM virtualizes APIC base as read only to guest, cpuid[0x1].x2apic doesn't matter after vcpu creation as long as user space VMM keeps KVM APIC BASE
value.

Contrary, can we depend on userspace VMM to set x2APIC in CPUID, but not do this in KVM? If userspace doesn't do it, we treat it as userspace's bug.

Plus, userspace anyway needs to set x2APIC in CPUID regardless whether you have done above here, correct?

I don't see the point of doing above in KVM because you are neither enforcing anything in KVM, nor you are reducing effort of userspace.

#90 lore raw

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 29 lines · Kai
Quoted 16 lines · Isaku Yamahata
Quoted 7 lines
Quoted 7 lines
(quoted diff) +7 -0
+	if (!e)
+		return -ENOMEM;
+	*e  = (struct kvm_cpuid_entry2) {
+		.function = 1,	/* Features for X2APIC */
+		.index = 0,
+		.eax = 0,
+		.ebx = 0,
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +5 -0
+		.ecx = 1ULL << 21,	/* X2APIC */
+		.edx = 0,
+	};
+	vcpu->arch.cpuid_entries = e;
+	vcpu->arch.cpuid_nent = 1;

As mentioned above, why doing it here? Won't be this be overwritten later in KVM_SET_CPUID2?

Quoted 11 lines

Yes, user space VMM can overwrite cpuid[0x1] and APIC base MSR.  But it doesn't
matter because it's a bug of user space VMM. user space VMM has to keep the consistency of cpuid and MSRs.
Because TDX module virtualizes cpuid[0x1].x2apic to fixed 1, KVM value doesn't matter after vcpu creation.
Because KVM virtualizes APIC base as read only to guest, cpuid[0x1].x2apic doesn't matter after vcpu creation as long as user space VMM keeps KVM APIC BASE
value.

Quoted 8 lines

Contrary, can we depend on userspace VMM to set x2APIC in CPUID, but not do this in KVM? If userspace doesn't do it, we treat it as userspace's bug.

Plus, userspace anyway needs to set x2APIC in CPUID regardless whether you have done above here, correct?

I don't see the point of doing above in KVM because you are neither enforcing anything in KVM, nor you are reducing effort of userspace.

Good idea. I can drop cpuid part from tdx_vcpu_create() and apic base part from tdx_vcpu_reset(). It needs to modify tdx_has_emulated_msr() to allow user space VMM to update APIC BASE MSR.

arch/x86/kvm/vmx/tdx.c +8 -1
index e045a8132639..46c82ce3ef46 100644
@@ -2624,7 +2624,14 @@ int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
 
 int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
 {
-       if (tdx_has_emulated_msr(msr->index, true))
+       if (tdx_has_emulated_msr(msr->index, true) ||
+           /*
+            * user space VMM should explicitly set to X2APIC mode as initial
+            * value that is deviated from the conventional case.
+            */
+           (msr->host_initiated && msr->index == MSR_IA32_APICBASE &&
+            (msr->data & ~MSR_IA32_APICBASE_BSP) ==
+            (APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC)))
                return kvm_set_msr_common(vcpu, msr);
        return 1;
 }

Just FYI, qemu needs the following change.

target/i386/kvm/tdx.c +16 -1
@@ -274,6 +274,11 @@ bool is_tdx_vm(void)
     return !!tdx_guest;
 }
 
+bool is_tdx_vm_finalized(void)
+{
+    return !!tdx_guest && tdx_guest->parent_obj.ready;
+}
+
 static inline uint32_t host_cpuid_reg(uint32_t function,
                                       uint32_t index, int reg)
 {
@@ -875,10 +880,20 @@ static void tdx_post_init_vcpus(void)
     TdxFirmwareEntry *hob;
     CPUState *cpu;
     int r;
+    uint64_t apic_base;
 
     hob = tdx_get_hob_entry(tdx_guest);
     CPU_FOREACH(cpu) {
-        apic_force_x2apic(X86_CPU(cpu)->apic_state);
+        X86CPU *x86_cpu = X86_CPU(cpu);
+
+        apic_force_x2apic(x86_cpu->apic_state);
+
+        apic_base = APIC_DEFAULT_ADDRESS | MSR_IA32_APICBASE_ENABLE |
+            MSR_IA32_APICBASE_EXTD;
+        if (cpu_is_bsp(x86_cpu))
+            apic_base |= MSR_IA32_APICBASE_BSP;
+        cpu_set_apic_base(x86_cpu->apic_state, apic_base);
+        kvm_put_apicbase(x86_cpu, apic_base);
 
         r = tdx_vcpu_ioctl(cpu, KVM_TDX_INIT_VCPU, 0, (void *)hob->address);
         if (r < 0) {
target/i386/kvm/tdx.h +2 -0
index 5cc0f730afa6..f77689464738 100644
@@ -59,8 +59,10 @@ typedef struct TdxGuest {
 
 #ifdef CONFIG_TDX
 bool is_tdx_vm(void);
+bool is_tdx_vm_finalized(void);
 #else
 #define is_tdx_vm() 0
+#define is_tdx_vm_finalized() false;
 #endif /* CONFIG_TDX */
 
 int tdx_kvm_init(MachineState *ms, Error **errp);
Signature · 1 line
Isaku Yamahata <[email protected]>
#91 lore raw

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 38 lines · Isaku Yamahata
Quoted 35 lines · Kai
Quoted 16 lines · Isaku Yamahata
Quoted 7 lines
Quoted 7 lines
(quoted diff) +7 -0
+	if (!e)
+		return -ENOMEM;
+	*e  = (struct kvm_cpuid_entry2) {
+		.function = 1,	/* Features for X2APIC */
+		.index = 0,
+		.eax = 0,
+		.ebx = 0,
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +5 -0
+		.ecx = 1ULL << 21,	/* X2APIC */
+		.edx = 0,
+	};
+	vcpu->arch.cpuid_entries = e;
+	vcpu->arch.cpuid_nent = 1;

As mentioned above, why doing it here? Won't be this be overwritten later in KVM_SET_CPUID2?

Quoted 11 lines

Yes, user space VMM can overwrite cpuid[0x1] and APIC base MSR.  But it doesn't
matter because it's a bug of user space VMM. user space VMM has to keep the consistency of cpuid and MSRs.
Because TDX module virtualizes cpuid[0x1].x2apic to fixed 1, KVM value doesn't matter after vcpu creation.
Because KVM virtualizes APIC base as read only to guest, cpuid[0x1].x2apic doesn't matter after vcpu creation as long as user space VMM keeps KVM APIC BASE
value.

Contrary, can we depend on userspace VMM to set x2APIC in CPUID, but not do this in KVM? If userspace doesn't do it, we treat it as userspace's bug.

Plus, userspace anyway needs to set x2APIC in CPUID regardless whether you have done above here, correct?

Quoted 6 lines
Quoted 2 lines

I don't see the point of doing above in KVM because you are neither enforcing anything in KVM, nor you are reducing effort of userspace.

Good idea. I can drop cpuid part from tdx_vcpu_create() and apic base part from tdx_vcpu_reset(). It needs to modify tdx_has_emulated_msr() to allow user space VMM to update APIC BASE MSR.

My personal preference would be:

1) In KVM_SET_CPUID2, we do sanity check of CPUIDs provided by userspace, and return error if not met (i.e X2APIC isn't advertised). We already have cases that KVM_SET_CPUID2 can fail, so extending to do TDX-specific check seems reasonable to me too.
2) For APIC_BASE, you can just initialize the MSR in tdx_vcpu_reset() and ignore any update (+pr_warn()?) to MSR_IA32_APIC_BASE.

But Sean may have different opinion especially for the CPUID part.

#92 lore raw

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 46 lines · Kai
Quoted 38 lines · Isaku Yamahata
Quoted 35 lines · Kai
Quoted 16 lines · Isaku Yamahata
Quoted 7 lines
Quoted 7 lines
(quoted diff) +7 -0
+	if (!e)
+		return -ENOMEM;
+	*e  = (struct kvm_cpuid_entry2) {
+		.function = 1,	/* Features for X2APIC */
+		.index = 0,
+		.eax = 0,
+		.ebx = 0,
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +5 -0
+		.ecx = 1ULL << 21,	/* X2APIC */
+		.edx = 0,
+	};
+	vcpu->arch.cpuid_entries = e;
+	vcpu->arch.cpuid_nent = 1;

As mentioned above, why doing it here? Won't be this be overwritten later in KVM_SET_CPUID2?

Quoted 11 lines

Yes, user space VMM can overwrite cpuid[0x1] and APIC base MSR.  But it doesn't
matter because it's a bug of user space VMM. user space VMM has to keep the consistency of cpuid and MSRs.
Because TDX module virtualizes cpuid[0x1].x2apic to fixed 1, KVM value doesn't matter after vcpu creation.
Because KVM virtualizes APIC base as read only to guest, cpuid[0x1].x2apic doesn't matter after vcpu creation as long as user space VMM keeps KVM APIC BASE
value.

Contrary, can we depend on userspace VMM to set x2APIC in CPUID, but not do this in KVM? If userspace doesn't do it, we treat it as userspace's bug.

Plus, userspace anyway needs to set x2APIC in CPUID regardless whether you have done above here, correct?

Quoted 6 lines
Quoted 2 lines

I don't see the point of doing above in KVM because you are neither enforcing anything in KVM, nor you are reducing effort of userspace.

Good idea. I can drop cpuid part from tdx_vcpu_create() and apic base part from tdx_vcpu_reset(). It needs to modify tdx_has_emulated_msr() to allow user space VMM to update APIC BASE MSR.

Quoted 6 lines

My personal preference would be:

1) In KVM_SET_CPUID2, we do sanity check of CPUIDs provided by userspace, and return error if not met (i.e X2APIC isn't advertised). We already have cases that KVM_SET_CPUID2 can fail, so extending to do TDX-specific check seems reasonable to me too.

This is moot. The current check does only check maxphys address bit size and specified xfeatures are supported by host. It's bare minimum for kvm to work. It doesn't try to check consistency.

Quoted 2 lines

2) For APIC_BASE, you can just initialize the MSR in tdx_vcpu_reset() and ignore any update (+pr_warn()?) to MSR_IA32_APIC_BASE.

The x86 common code for KVM_CREATE_VCPU, kvm_arch_vcpu_create(), calls vcpu_create, creates lapic, and calls vcpu_reset().

Setting ACPI BASE MSR with X2APIC enabled, checks if cpuid x2apic bit is set. Please notice guest_cpuid_has(vcpu, X86_FEATURE_X2APIC) in kvm_set_apic_base(). To work around it, one way is set cpuid artificially in create method as this patch does. Other way would be to introduce another version of kvm_set_apic_base() that doesn't check cpuid dedicated for this purpose. The third option is to make it user space responsibility to set initial reset value of APIC BASE MSR.

Which option do you prefer?

Thanks,

Quoted 3 lines

But Sean may have different opinion especially for the CPUID part.

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

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 75 lines · Isaku Yamahata
Quoted 46 lines · Kai
Quoted 38 lines · Isaku Yamahata
Quoted 35 lines · Kai
Quoted 16 lines · Isaku Yamahata
Quoted 7 lines
Quoted 7 lines
(quoted diff) +7 -0
+	if (!e)
+		return -ENOMEM;
+	*e  = (struct kvm_cpuid_entry2) {
+		.function = 1,	/* Features for X2APIC */
+		.index = 0,
+		.eax = 0,
+		.ebx = 0,
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +5 -0
+		.ecx = 1ULL << 21,	/* X2APIC */
+		.edx = 0,
+	};
+	vcpu->arch.cpuid_entries = e;
+	vcpu->arch.cpuid_nent = 1;

As mentioned above, why doing it here? Won't be this be overwritten later in KVM_SET_CPUID2?

Quoted 11 lines

Yes, user space VMM can overwrite cpuid[0x1] and APIC base MSR.  But it doesn't
matter because it's a bug of user space VMM. user space VMM has to keep the consistency of cpuid and MSRs.
Because TDX module virtualizes cpuid[0x1].x2apic to fixed 1, KVM value doesn't matter after vcpu creation.
Because KVM virtualizes APIC base as read only to guest, cpuid[0x1].x2apic doesn't matter after vcpu creation as long as user space VMM keeps KVM APIC BASE
value.

Contrary, can we depend on userspace VMM to set x2APIC in CPUID, but not do this in KVM? If userspace doesn't do it, we treat it as userspace's bug.

Plus, userspace anyway needs to set x2APIC in CPUID regardless whether you have done above here, correct?

Quoted 6 lines
Quoted 2 lines

I don't see the point of doing above in KVM because you are neither enforcing anything in KVM, nor you are reducing effort of userspace.

Good idea. I can drop cpuid part from tdx_vcpu_create() and apic base part from tdx_vcpu_reset(). It needs to modify tdx_has_emulated_msr() to allow user space VMM to update APIC BASE MSR.

Quoted 6 lines

My personal preference would be:

1) In KVM_SET_CPUID2, we do sanity check of CPUIDs provided by userspace, and return error if not met (i.e X2APIC isn't advertised). We already have cases that KVM_SET_CPUID2 can fail, so extending to do TDX-specific check seems reasonable to me too.

This is moot. The current check does only check maxphys address bit size and specified xfeatures are supported by host. It's bare minimum for kvm to work. It doesn't try to check consistency.

Quoted 2 lines

2) For APIC_BASE, you can just initialize the MSR in tdx_vcpu_reset() and ignore any update (+pr_warn()?) to MSR_IA32_APIC_BASE.

The x86 common code for KVM_CREATE_VCPU, kvm_arch_vcpu_create(), calls vcpu_create, creates lapic, and calls vcpu_reset().

Setting ACPI BASE MSR with X2APIC enabled, checks if cpuid x2apic bit is set. Please notice guest_cpuid_has(vcpu, X86_FEATURE_X2APIC) in kvm_set_apic_base(). To work around it, one way is set cpuid artificially in create method as this patch does. Other way would be to introduce another version of kvm_set_apic_base() that doesn't check cpuid dedicated for this purpose. The third option is to make it user space responsibility to set initial reset value of APIC BASE MSR.

Quoted 2 lines

Which option do you prefer?

I just recall you have already set all CPUIDs via tdx_td_init(). I would do below:

1) keep all CPUIDs in tdx_td_init(), and make vcpu->cpuid point to that.
2) Ignore KVM_SET_CPUID2 for TDX guest (+ pr_warn(), etc).
3) Set TDX-fixed CPU registers/msrs, etc in reset_vcpu().

#94 lore raw

Re: [PATCH v11 023/113] KVM: TDX: allocate/free TDX vcpu structure

Quoted 79 lines · Kai
Quoted 75 lines · Isaku Yamahata
Quoted 46 lines · Kai
Quoted 38 lines · Isaku Yamahata
Quoted 35 lines · Kai
Quoted 16 lines · Isaku Yamahata
Quoted 7 lines
Quoted 7 lines
(quoted diff) +7 -0
+	if (!e)
+		return -ENOMEM;
+	*e  = (struct kvm_cpuid_entry2) {
+		.function = 1,	/* Features for X2APIC */
+		.index = 0,
+		.eax = 0,
+		.ebx = 0,
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +5 -0
+		.ecx = 1ULL << 21,	/* X2APIC */
+		.edx = 0,
+	};
+	vcpu->arch.cpuid_entries = e;
+	vcpu->arch.cpuid_nent = 1;

As mentioned above, why doing it here? Won't be this be overwritten later in KVM_SET_CPUID2?

Quoted 11 lines

Yes, user space VMM can overwrite cpuid[0x1] and APIC base MSR.  But it doesn't
matter because it's a bug of user space VMM. user space VMM has to keep the consistency of cpuid and MSRs.
Because TDX module virtualizes cpuid[0x1].x2apic to fixed 1, KVM value doesn't matter after vcpu creation.
Because KVM virtualizes APIC base as read only to guest, cpuid[0x1].x2apic doesn't matter after vcpu creation as long as user space VMM keeps KVM APIC BASE
value.

Contrary, can we depend on userspace VMM to set x2APIC in CPUID, but not do this in KVM? If userspace doesn't do it, we treat it as userspace's bug.

Plus, userspace anyway needs to set x2APIC in CPUID regardless whether you have done above here, correct?

Quoted 6 lines
Quoted 2 lines

I don't see the point of doing above in KVM because you are neither enforcing anything in KVM, nor you are reducing effort of userspace.

Good idea. I can drop cpuid part from tdx_vcpu_create() and apic base part from tdx_vcpu_reset(). It needs to modify tdx_has_emulated_msr() to allow user space VMM to update APIC BASE MSR.

Quoted 6 lines

My personal preference would be:

1) In KVM_SET_CPUID2, we do sanity check of CPUIDs provided by userspace, and return error if not met (i.e X2APIC isn't advertised). We already have cases that KVM_SET_CPUID2 can fail, so extending to do TDX-specific check seems reasonable to me too.

This is moot. The current check does only check maxphys address bit size and specified xfeatures are supported by host. It's bare minimum for kvm to work. It doesn't try to check consistency.

Quoted 2 lines

2) For APIC_BASE, you can just initialize the MSR in tdx_vcpu_reset() and ignore any update (+pr_warn()?) to MSR_IA32_APIC_BASE.

The x86 common code for KVM_CREATE_VCPU, kvm_arch_vcpu_create(), calls vcpu_create, creates lapic, and calls vcpu_reset().

Setting ACPI BASE MSR with X2APIC enabled, checks if cpuid x2apic bit is set. Please notice guest_cpuid_has(vcpu, X86_FEATURE_X2APIC) in kvm_set_apic_base(). To work around it, one way is set cpuid artificially in create method as this patch does. Other way would be to introduce another version of kvm_set_apic_base() that doesn't check cpuid dedicated for this purpose. The third option is to make it user space responsibility to set initial reset value of APIC BASE MSR.

Quoted 2 lines

Which option do you prefer?

Quoted 6 lines

I just recall you have already set all CPUIDs via tdx_td_init(). I would do below:

1) keep all CPUIDs in tdx_td_init(), and make vcpu->cpuid point to that.
2) Ignore KVM_SET_CPUID2 for TDX guest (+ pr_warn(), etc).
3) Set TDX-fixed CPU registers/msrs, etc in reset_vcpu().

Finally I come up with the following flow.

- KVM_CREATE_VCPU
- tdx_vcpu_create()
no cpuid, no msr operation
- KVM_SET_CPUID2
user space has to set cpuid...x2apic=1
- KVM_TDX_INIT_VCPU
tdx_vcpu_ioctl() sets APIC_BASE MSR to x2apic enabled.
Here if user space VMM doesn't set cpuid properly, it results in error.

ACPI_BASE MSR is read only for both user space VMM and guest TD. cpuid:
After KVM_TDX_INIT_VCPU, user space VMM can update it by KVM_SET_CPUID2. KVM doesn't care. Guest TD see cpuid.x2apic value virtualized by TDX module while KVM internally doesn't use the value because APIC_BASE won't change.

Thanks,

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

PATCH v11 24/113 KVM: TDX: Do TDX specific vcpu initialization

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TD guest vcpu need to be configured before ready to run which requests addtional information from Device model (e.g. qemu), one 64bit value is passed to vcpu's RCX as an initial value. Repurpose KVM_MEMORY_ENCRYPT_OP to vcpu-scope and add new sub-commands KVM_TDX_INIT_VCPU under it for such additional vcpu configuration.

Add callback for kvm vCPU-scoped operations of KVM_MEMORY_ENCRYPT_OP and add a new subcommand, KVM_TDX_INIT_VCPU, for further vcpu initialization.

---
 arch/x86/include/asm/kvm-x86-ops.h    |   1 +
 arch/x86/include/asm/kvm_host.h       |   1 +
 arch/x86/include/uapi/asm/kvm.h       |   1 +
 arch/x86/kvm/vmx/main.c               |   9 ++
 arch/x86/kvm/vmx/tdx.c                | 147 +++++++++++++++++++++++++-
 arch/x86/kvm/vmx/tdx.h                |   7 ++
 arch/x86/kvm/vmx/x86_ops.h            |  10 +-
 arch/x86/kvm/x86.c                    |   6 ++
 tools/arch/x86/include/uapi/asm/kvm.h |   1 +
 9 files changed, 178 insertions(+), 5 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 1a27f3aee982..e3e9b1c2599b 100644
@@ -123,6 +123,7 @@ KVM_X86_OP(enable_smi_window)
 #endif
 KVM_X86_OP_OPTIONAL(dev_mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_ioctl)
+KVM_X86_OP_OPTIONAL(vcpu_mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_register_region)
 KVM_X86_OP_OPTIONAL(mem_enc_unregister_region)
 KVM_X86_OP_OPTIONAL(vm_copy_enc_context_from)
arch/x86/include/asm/kvm_host.h +1 -0
index 30f4ddb18548..35773f925cc5 100644
@@ -1698,6 +1698,7 @@ struct kvm_x86_ops {
 
 	int (*dev_mem_enc_ioctl)(void __user *argp);
 	int (*mem_enc_ioctl)(struct kvm *kvm, void __user *argp);
+	int (*vcpu_mem_enc_ioctl)(struct kvm_vcpu *vcpu, void __user *argp);
 	int (*mem_enc_register_region)(struct kvm *kvm, struct kvm_enc_region *argp);
 	int (*mem_enc_unregister_region)(struct kvm *kvm, struct kvm_enc_region *argp);
 	int (*vm_copy_enc_context_from)(struct kvm *kvm, unsigned int source_fd);
arch/x86/include/uapi/asm/kvm.h +1 -0
index b8f28d86d4fd..9236c1699c48 100644
@@ -536,6 +536,7 @@ struct kvm_pmu_event_filter {
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
 	KVM_TDX_INIT_VM,
+	KVM_TDX_INIT_VCPU,
 
 	KVM_TDX_CMD_NR_MAX,
 };
arch/x86/kvm/vmx/main.c +9 -0
index 59813ca05f36..23b3ffc3fe23 100644
@@ -103,6 +103,14 @@ static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 	return tdx_vm_ioctl(kvm, argp);
 }
 
+static int vt_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
+{
+	if (!is_td_vcpu(vcpu))
+		return -EINVAL;
+
+	return tdx_vcpu_ioctl(vcpu, argp);
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
@@ -249,6 +257,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.dev_mem_enc_ioctl = tdx_dev_ioctl,
 	.mem_enc_ioctl = vt_mem_enc_ioctl,
+	.vcpu_mem_enc_ioctl = vt_vcpu_mem_enc_ioctl,
 };
 
 struct kvm_x86_init_ops vt_init_ops __initdata = {
arch/x86/kvm/vmx/tdx.c +146 -1

177 lines not on this page · raw message

arch/x86/kvm/vmx/tdx.h +7 -0
index af7fdc1516d5..e909883d60fa 100644
@@ -17,12 +17,19 @@ struct kvm_tdx {
 	u64 xfam;
 	int hkid;
 
+	bool finalized;
+
 	u64 tsc_offset;
 };
 
 struct vcpu_tdx {
 	struct kvm_vcpu	vcpu;
 
+	unsigned long tdvpr_pa;
+	unsigned long *tdvpx_pa;
+
+	bool vcpu_initialized;
+
 	/*
 	 * Dummy to make pmu_intel not corrupt memory.
 	 * TODO: Support PMU for TDX.  Future work.
arch/x86/kvm/vmx/x86_ops.h +6 -4
index 37ab2cfd35bc..fba8d0800597 100644
@@ -148,11 +148,12 @@ int tdx_vm_init(struct kvm *kvm);
 void tdx_mmu_release_hkid(struct kvm *kvm);
 void tdx_vm_free(struct kvm *kvm);
 
-int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
-
 int tdx_vcpu_create(struct kvm_vcpu *vcpu);
 void tdx_vcpu_free(struct kvm_vcpu *vcpu);
 void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event);
+
+int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
+int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp);
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
 static inline void tdx_hardware_unsetup(void) {}
@@ -165,11 +166,12 @@ static inline void tdx_mmu_release_hkid(struct kvm *kvm) {}
 static inline void tdx_flush_shadow_all_private(struct kvm *kvm) {}
 static inline void tdx_vm_free(struct kvm *kvm) {}
 
-static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
-
 static inline int tdx_vcpu_create(struct kvm_vcpu *vcpu) { return -EOPNOTSUPP; }
 static inline void tdx_vcpu_free(struct kvm_vcpu *vcpu) {}
 static inline void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event) {}
+
+static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
+static inline int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp) { return -EOPNOTSUPP; }
 #endif
 
 #endif /* __KVM_X86_VMX_X86_OPS_H */
arch/x86/kvm/x86.c +6 -0
index e8bc66031a1d..d548d3af6428 100644
@@ -5976,6 +5976,12 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
 	case KVM_SET_DEVICE_ATTR:
 		r = kvm_vcpu_ioctl_device_attr(vcpu, ioctl, argp);
 		break;
+	case KVM_MEMORY_ENCRYPT_OP:
+		r = -ENOTTY;
+		if (!kvm_x86_ops.vcpu_mem_enc_ioctl)
+			goto out;
+		r = kvm_x86_ops.vcpu_mem_enc_ioctl(vcpu, argp);
+		break;
 	default:
 		r = -EINVAL;
 	}
tools/arch/x86/include/uapi/asm/kvm.h +1 -0
index eb800965b589..6971f1288043 100644
@@ -531,6 +531,7 @@ struct kvm_pmu_event_filter {
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
 	KVM_TDX_INIT_VM,
+	KVM_TDX_INIT_VCPU,
 
 	KVM_TDX_CMD_NR_MAX,
 };
Signature · 1 line
2.25.1
#96 lore raw

Re: [PATCH v11 024/113] KVM: TDX: Do TDX specific vcpu initialization

Quoted 8 lines · On Thu, 12 Jan 2023 08:31:32 -0800 [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TD guest vcpu need to be configured before ready to run which requests addtional information from Device model (e.g. qemu), one 64bit value is passed to vcpu's RCX as an initial value. Repurpose KVM_MEMORY_ENCRYPT_OP to vcpu-scope and add new sub-commands KVM_TDX_INIT_VCPU under it for such additional vcpu configuration.

Better add more details for this mystic value to save the review efforts.

For exmaple, refining the above part as:

----

TD hands-off block(HOB) is used to pass the information from VMM to TD virtual firmware(TDVF). Before KVM calls Intel TDX module to launch TDVF, the address of HOB must be placed in the guest RCX.

Extend KVM_MEMORY_ENCRYPT_OP to vcpu-scope and add new... so that TDH.VP.INIT can take the address of HOB from QEMU and place it in the guest RCX when initializing a TDX vCPU.

----

The below paragraph seems repeating the end of the first paragraph. Guess it can be refined or removed.

Quoted 3 lines

Add callback for kvm vCPU-scoped operations of KVM_MEMORY_ENCRYPT_OP and add a new subcommand, KVM_TDX_INIT_VCPU, for further vcpu initialization.

PS: I am curious if the value of guest RCX on each VCPU will be configured differently? (It seems they are the same according to the code of tdx-qemu)

If yes, then it is just an approach to configure the value (even it is through TDH.VP.XXX). It should be configured in the domain level in KVM. The TDX vCPU creation and initialization can be moved into tdx_vcpu_create() and TDH.VP.INIT can take the value from a per-vm data structure.

Quoted 107 lines
---
 arch/x86/include/asm/kvm-x86-ops.h    |   1 +
 arch/x86/include/asm/kvm_host.h       |   1 +
 arch/x86/include/uapi/asm/kvm.h       |   1 +
 arch/x86/kvm/vmx/main.c               |   9 ++
 arch/x86/kvm/vmx/tdx.c                | 147 +++++++++++++++++++++++++-
 arch/x86/kvm/vmx/tdx.h                |   7 ++
 arch/x86/kvm/vmx/x86_ops.h            |  10 +-
 arch/x86/kvm/x86.c                    |   6 ++
 tools/arch/x86/include/uapi/asm/kvm.h |   1 +
 9 files changed, 178 insertions(+), 5 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 1a27f3aee982..e3e9b1c2599b 100644
@@ -123,6 +123,7 @@ KVM_X86_OP(enable_smi_window)
 #endif
 KVM_X86_OP_OPTIONAL(dev_mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_ioctl)
+KVM_X86_OP_OPTIONAL(vcpu_mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_register_region)
 KVM_X86_OP_OPTIONAL(mem_enc_unregister_region)
 KVM_X86_OP_OPTIONAL(vm_copy_enc_context_from)
arch/x86/include/asm/kvm_host.h +1 -0
index 30f4ddb18548..35773f925cc5 100644
@@ -1698,6 +1698,7 @@ struct kvm_x86_ops {
 
 	int (*dev_mem_enc_ioctl)(void __user *argp);
 	int (*mem_enc_ioctl)(struct kvm *kvm, void __user *argp);
+	int (*vcpu_mem_enc_ioctl)(struct kvm_vcpu *vcpu, void __user *argp);
 	int (*mem_enc_register_region)(struct kvm *kvm, struct kvm_enc_region *argp);
 	int (*mem_enc_unregister_region)(struct kvm *kvm, struct kvm_enc_region *argp);
 	int (*vm_copy_enc_context_from)(struct kvm *kvm, unsigned int source_fd);
arch/x86/include/uapi/asm/kvm.h +1 -0
index b8f28d86d4fd..9236c1699c48 100644
@@ -536,6 +536,7 @@ struct kvm_pmu_event_filter {
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
 	KVM_TDX_INIT_VM,
+	KVM_TDX_INIT_VCPU,
 
 	KVM_TDX_CMD_NR_MAX,
 };
arch/x86/kvm/vmx/main.c +9 -0
index 59813ca05f36..23b3ffc3fe23 100644
@@ -103,6 +103,14 @@ static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 	return tdx_vm_ioctl(kvm, argp);
 }
 
+static int vt_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
+{
+	if (!is_td_vcpu(vcpu))
+		return -EINVAL;
+
+	return tdx_vcpu_ioctl(vcpu, argp);
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
@@ -249,6 +257,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.dev_mem_enc_ioctl = tdx_dev_ioctl,
 	.mem_enc_ioctl = vt_mem_enc_ioctl,
+	.vcpu_mem_enc_ioctl = vt_vcpu_mem_enc_ioctl,
 };
 
 struct kvm_x86_init_ops vt_init_ops __initdata = {
arch/x86/kvm/vmx/tdx.c +10 -0
index 099f0737a5aa..e2f5a07ad4e5 100644
@@ -49,6 +49,11 @@ static __always_inline hpa_t set_hkid_to_hpa(hpa_t pa, u16 hkid)
 	return pa | ((hpa_t)hkid << boot_cpu_data.x86_phys_bits);
 }
 
+static inline bool is_td_vcpu_created(struct vcpu_tdx *tdx)
+{
+	return tdx->tdvpr_pa;
+}
+
 static inline bool is_td_created(struct kvm_tdx *kvm_tdx)
 {
 	return kvm_tdx->tdr_pa;
@@ -65,6 +70,11 @@ static inline bool is_hkid_assigned(struct kvm_tdx *kvm_tdx)
 	return kvm_tdx->hkid > 0;
 }
 
+static inline bool is_td_finalized(struct kvm_tdx *kvm_tdx)
+{
+	return kvm_tdx->finalized;
+}
+
 static void tdx_clear_page(unsigned long page_pa)
 {
 	const void *zero_page = (const void *) __va(page_to_phys(ZERO_PAGE(0)));
@@ -327,7 +337,21 @@ int tdx_vcpu_create(struct kvm_vcpu *vcpu)
 

arch/x86/kvm/vmx/tdx.c int tdx_vcpu_create(struct kvm_vcpu *vcpu) · line 346

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +7 -1
 void tdx_vcpu_free(struct kvm_vcpu *vcpu)
 {
-	/* This is stub for now.  More logic will come. */
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	int i;
+
+	/* Can't reclaim or free pages if teardown failed. */
+	if (is_hkid_assigned(to_kvm_tdx(vcpu->kvm)))
+		return;
+

Should we have an WARN_ON_ONCE here?

Quoted 101 lines
arch/x86/kvm/vmx/tdx.c +87 -0
+	if (tdx->tdvpx_pa) {
+		for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++)
+			tdx_reclaim_td_page(tdx->tdvpx_pa[i]);
+		kfree(tdx->tdvpx_pa);
+		tdx->tdvpx_pa = NULL;
+	}
+	tdx_reclaim_td_page(tdx->tdvpr_pa);
+	tdx->tdvpr_pa = 0;
 }
 
 void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
@@ -337,6 +361,8 @@ void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
 	/* TDX doesn't support INIT event. */
 	if (WARN_ON_ONCE(init_event))
 		goto td_bugged;
+	if (WARN_ON_ONCE(is_td_vcpu_created(to_tdx(vcpu))))
+		goto td_bugged;
 
 	/* TDX rquires X2APIC. */
 	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
@@ -791,6 +817,125 @@ int tdx_vm_ioctl(struct kvm *kvm, void __user *argp)
 	return r;
 }
 
+static int tdx_td_vcpu_init(struct kvm_vcpu *vcpu, u64 vcpu_rcx)
+{
+	struct kvm_tdx *kvm_tdx = to_kvm_tdx(vcpu->kvm);
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	unsigned long *tdvpx_pa = NULL;
+	unsigned long tdvpr_pa;
+	unsigned long va;
+	int ret, i;
+	u64 err;
+
+	if (is_td_vcpu_created(tdx))
+		return -EINVAL;
+
+	va = __get_free_page(GFP_KERNEL_ACCOUNT);
+	if (!va)
+		return -ENOMEM;
+	tdvpr_pa = __pa(va);
+
+	tdvpx_pa = kcalloc(tdx_caps.tdvpx_nr_pages, sizeof(*tdx->tdvpx_pa),
+			   GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+	if (!tdvpx_pa) {
+		ret = -ENOMEM;
+		goto free_tdvpr;
+	}
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		va = __get_free_page(GFP_KERNEL_ACCOUNT);
+		if (!va)
+			goto free_tdvpx;
+		tdvpx_pa[i] = __pa(va);
+	}
+
+	err = tdh_vp_create(kvm_tdx->tdr_pa, tdvpr_pa);
+	if (WARN_ON_ONCE(err)) {
+		ret = -EIO;
+		pr_tdx_error(TDH_VP_CREATE, err, NULL);
+		goto td_bugged_free_tdvpx;
+	}
+	tdx->tdvpr_pa = tdvpr_pa;
+
+	tdx->tdvpx_pa = tdvpx_pa;
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		err = tdh_vp_addcx(tdx->tdvpr_pa, tdvpx_pa[i]);
+		if (WARN_ON_ONCE(err)) {
+			ret = -EIO;
+			pr_tdx_error(TDH_VP_ADDCX, err, NULL);
+			for (; i < tdx_caps.tdvpx_nr_pages; i++) {
+				free_page((unsigned long)__va(tdvpx_pa[i]));
+				tdvpx_pa[i] = 0;
+			}
+			goto td_bugged;
+		}
+	}
+
+	err = tdh_vp_init(tdx->tdvpr_pa, vcpu_rcx);
+	if (WARN_ON_ONCE(err)) {
+		ret = -EIO;
+		pr_tdx_error(TDH_VP_INIT, err, NULL);
+		goto td_bugged;
+	}
+
+	vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+
+	return 0;
+
+td_bugged_free_tdvpx:
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		free_page((unsigned long)__va(tdvpx_pa[i]));
+		tdvpx_pa[i] = 0;
+	}
+	kfree(tdvpx_pa);
+td_bugged:
+	vcpu->kvm->vm_bugged = true;
+	return ret;
+
+free_tdvpx:
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++)
+		if (tdvpx_pa[i])

arch/x86/kvm/vmx/tdx.c int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) · line 906

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+			free_page((unsigned long)__va(tdvpx_pa[i]));
+	kfree(tdvpx_pa);
+	tdx->tdvpx_pa = NULL;
+free_tdvpr:
+	if (tdvpr_pa)
+		free_page((unsigned long)__va(tdvpr_pa));
+	tdx->tdvpr_pa = 0;
+
+	return ret;
+}

Same comments with using vm_bugged in the previous patch.

Quoted 122 lines
arch/x86/kvm/vmx/tdx.c +32 -0
+
+int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
+{
+	struct kvm_tdx *kvm_tdx = to_kvm_tdx(vcpu->kvm);
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	struct kvm_tdx_cmd cmd;
+	int ret;
+
+	if (tdx->vcpu_initialized)
+		return -EINVAL;
+
+	if (!is_hkid_assigned(kvm_tdx) || is_td_finalized(kvm_tdx))
+		return -EINVAL;
+
+	if (copy_from_user(&cmd, argp, sizeof(cmd)))
+		return -EFAULT;
+
+	if (cmd.error || cmd.unused)
+		return -EINVAL;
+
+	/* Currently only KVM_TDX_INTI_VCPU is defined for vcpu operation. */
+	if (cmd.flags || cmd.id != KVM_TDX_INIT_VCPU)
+		return -EINVAL;
+
+	ret = tdx_td_vcpu_init(vcpu, (u64)cmd.data);
+	if (ret)
+		return ret;
+
+	tdx->vcpu_initialized = true;
+	return 0;
+}
+
 static int __init tdx_module_setup(void)
 {
 	const struct tdsysinfo_struct *tdsysinfo;
arch/x86/kvm/vmx/tdx.h +7 -0
index af7fdc1516d5..e909883d60fa 100644
@@ -17,12 +17,19 @@ struct kvm_tdx {
 	u64 xfam;
 	int hkid;
 
+	bool finalized;
+
 	u64 tsc_offset;
 };
 
 struct vcpu_tdx {
 	struct kvm_vcpu	vcpu;
 
+	unsigned long tdvpr_pa;
+	unsigned long *tdvpx_pa;
+
+	bool vcpu_initialized;
+
 	/*
 	 * Dummy to make pmu_intel not corrupt memory.
 	 * TODO: Support PMU for TDX.  Future work.
arch/x86/kvm/vmx/x86_ops.h +6 -4
index 37ab2cfd35bc..fba8d0800597 100644
@@ -148,11 +148,12 @@ int tdx_vm_init(struct kvm *kvm);
 void tdx_mmu_release_hkid(struct kvm *kvm);
 void tdx_vm_free(struct kvm *kvm);
 
-int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
-
 int tdx_vcpu_create(struct kvm_vcpu *vcpu);
 void tdx_vcpu_free(struct kvm_vcpu *vcpu);
 void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event);
+
+int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
+int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp);
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
 static inline void tdx_hardware_unsetup(void) {}
@@ -165,11 +166,12 @@ static inline void tdx_mmu_release_hkid(struct kvm *kvm) {}
 static inline void tdx_flush_shadow_all_private(struct kvm *kvm) {}
 static inline void tdx_vm_free(struct kvm *kvm) {}
 
-static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
-
 static inline int tdx_vcpu_create(struct kvm_vcpu *vcpu) { return -EOPNOTSUPP; }
 static inline void tdx_vcpu_free(struct kvm_vcpu *vcpu) {}
 static inline void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event) {}
+
+static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
+static inline int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp) { return -EOPNOTSUPP; }
 #endif
 
 #endif /* __KVM_X86_VMX_X86_OPS_H */
arch/x86/kvm/x86.c +6 -0
index e8bc66031a1d..d548d3af6428 100644
@@ -5976,6 +5976,12 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
 	case KVM_SET_DEVICE_ATTR:
 		r = kvm_vcpu_ioctl_device_attr(vcpu, ioctl, argp);
 		break;
+	case KVM_MEMORY_ENCRYPT_OP:
+		r = -ENOTTY;
+		if (!kvm_x86_ops.vcpu_mem_enc_ioctl)
+			goto out;
+		r = kvm_x86_ops.vcpu_mem_enc_ioctl(vcpu, argp);
+		break;
 	default:
 		r = -EINVAL;
 	}
tools/arch/x86/include/uapi/asm/kvm.h +1 -0
index eb800965b589..6971f1288043 100644
@@ -531,6 +531,7 @@ struct kvm_pmu_event_filter {
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
 	KVM_TDX_INIT_VM,
+	KVM_TDX_INIT_VCPU,
 
 	KVM_TDX_CMD_NR_MAX,
 };
#97 lore raw

Re: [PATCH v11 024/113] KVM: TDX: Do TDX specific vcpu initialization

Quoted 22 lines · Zhi Wang
Quoted 8 lines · On Thu, 12 Jan 2023 08:31:32 -0800 [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TD guest vcpu need to be configured before ready to run which requests addtional information from Device model (e.g. qemu), one 64bit value is passed to vcpu's RCX as an initial value. Repurpose KVM_MEMORY_ENCRYPT_OP to vcpu-scope and add new sub-commands KVM_TDX_INIT_VCPU under it for such additional vcpu configuration.

Better add more details for this mystic value to save the review efforts.

For exmaple, refining the above part as:

----

TD hands-off block(HOB) is used to pass the information from VMM to TD virtual firmware(TDVF). Before KVM calls Intel TDX module to launch TDVF, the address of HOB must be placed in the guest RCX.

Quoted 13 lines

Extend KVM_MEMORY_ENCRYPT_OP to vcpu-scope and add new... so that TDH.VP.INIT can take the address of HOB from QEMU and place it in the guest RCX when initializing a TDX vCPU.

----

The below paragraph seems repeating the end of the first paragraph. Guess it can be refined or removed.

Quoted 3 lines

Add callback for kvm vCPU-scoped operations of KVM_MEMORY_ENCRYPT_OP and add a new subcommand, KVM_TDX_INIT_VCPU, for further vcpu initialization.

I don't think it's good idea to mention about new terminology HOB and TDVF. We can say, VMM can pass one parameter.
Here is the updated one.

TD guest vcpu needs TDX specific initialization before running. Repurpose KVM_MEMORY_ENCRYPT_OP to vcpu-scope, add a new sub-command KVM_TDX_INIT_VCPU, and implement the callback for it.

Quoted 7 lines

PS: I am curious if the value of guest RCX on each VCPU will be configured differently? (It seems they are the same according to the code of tdx-qemu)

If yes, then it is just an approach to configure the value (even it is through TDH.VP.XXX). It should be configured in the domain level in KVM. The TDX vCPU creation and initialization can be moved into tdx_vcpu_create() and TDH.VP.INIT can take the value from a per-vm data structure.

RCX can be set for each VCPUs as ABI (or TDX SEAMCALL API) between VMM and vcpu initial value. It's convention between user space VMM(qemu) and guest firmware(TDVF) to pass same RCX value for all vcpu. So KVM shouldn't enforce same RCX value for all vcpus. KVM should allow user space VMM to set the value for each vcpus.

Quoted 107 lines
Quoted 107 lines
---
 arch/x86/include/asm/kvm-x86-ops.h    |   1 +
 arch/x86/include/asm/kvm_host.h       |   1 +
 arch/x86/include/uapi/asm/kvm.h       |   1 +
 arch/x86/kvm/vmx/main.c               |   9 ++
 arch/x86/kvm/vmx/tdx.c                | 147 +++++++++++++++++++++++++-
 arch/x86/kvm/vmx/tdx.h                |   7 ++
 arch/x86/kvm/vmx/x86_ops.h            |  10 +-
 arch/x86/kvm/x86.c                    |   6 ++
 tools/arch/x86/include/uapi/asm/kvm.h |   1 +
 9 files changed, 178 insertions(+), 5 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 1a27f3aee982..e3e9b1c2599b 100644
@@ -123,6 +123,7 @@ KVM_X86_OP(enable_smi_window)
 #endif
 KVM_X86_OP_OPTIONAL(dev_mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_ioctl)
+KVM_X86_OP_OPTIONAL(vcpu_mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_register_region)
 KVM_X86_OP_OPTIONAL(mem_enc_unregister_region)
 KVM_X86_OP_OPTIONAL(vm_copy_enc_context_from)
arch/x86/include/asm/kvm_host.h +1 -0
index 30f4ddb18548..35773f925cc5 100644
@@ -1698,6 +1698,7 @@ struct kvm_x86_ops {
 
 	int (*dev_mem_enc_ioctl)(void __user *argp);
 	int (*mem_enc_ioctl)(struct kvm *kvm, void __user *argp);
+	int (*vcpu_mem_enc_ioctl)(struct kvm_vcpu *vcpu, void __user *argp);
 	int (*mem_enc_register_region)(struct kvm *kvm, struct kvm_enc_region *argp);
 	int (*mem_enc_unregister_region)(struct kvm *kvm, struct kvm_enc_region *argp);
 	int (*vm_copy_enc_context_from)(struct kvm *kvm, unsigned int source_fd);
arch/x86/include/uapi/asm/kvm.h +1 -0
index b8f28d86d4fd..9236c1699c48 100644
@@ -536,6 +536,7 @@ struct kvm_pmu_event_filter {
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
 	KVM_TDX_INIT_VM,
+	KVM_TDX_INIT_VCPU,
 
 	KVM_TDX_CMD_NR_MAX,
 };
arch/x86/kvm/vmx/main.c +9 -0
index 59813ca05f36..23b3ffc3fe23 100644
@@ -103,6 +103,14 @@ static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 	return tdx_vm_ioctl(kvm, argp);
 }
 
+static int vt_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
+{
+	if (!is_td_vcpu(vcpu))
+		return -EINVAL;
+
+	return tdx_vcpu_ioctl(vcpu, argp);
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
@@ -249,6 +257,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.dev_mem_enc_ioctl = tdx_dev_ioctl,
 	.mem_enc_ioctl = vt_mem_enc_ioctl,
+	.vcpu_mem_enc_ioctl = vt_vcpu_mem_enc_ioctl,
 };
 
 struct kvm_x86_init_ops vt_init_ops __initdata = {
arch/x86/kvm/vmx/tdx.c +10 -0
index 099f0737a5aa..e2f5a07ad4e5 100644
@@ -49,6 +49,11 @@ static __always_inline hpa_t set_hkid_to_hpa(hpa_t pa, u16 hkid)
 	return pa | ((hpa_t)hkid << boot_cpu_data.x86_phys_bits);
 }
 
+static inline bool is_td_vcpu_created(struct vcpu_tdx *tdx)
+{
+	return tdx->tdvpr_pa;
+}
+
 static inline bool is_td_created(struct kvm_tdx *kvm_tdx)
 {
 	return kvm_tdx->tdr_pa;
@@ -65,6 +70,11 @@ static inline bool is_hkid_assigned(struct kvm_tdx *kvm_tdx)
 	return kvm_tdx->hkid > 0;
 }
 
+static inline bool is_td_finalized(struct kvm_tdx *kvm_tdx)
+{
+	return kvm_tdx->finalized;
+}
+
 static void tdx_clear_page(unsigned long page_pa)
 {
 	const void *zero_page = (const void *) __va(page_to_phys(ZERO_PAGE(0)));
@@ -327,7 +337,21 @@ int tdx_vcpu_create(struct kvm_vcpu *vcpu)
 
Quoted 12 lines

arch/x86/kvm/vmx/tdx.c int tdx_vcpu_create(struct kvm_vcpu *vcpu) · line 346

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +7 -1
 void tdx_vcpu_free(struct kvm_vcpu *vcpu)
 {
-	/* This is stub for now.  More logic will come. */
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	int i;
+
+	/* Can't reclaim or free pages if teardown failed. */
+	if (is_hkid_assigned(to_kvm_tdx(vcpu->kvm)))
+		return;
+

Should we have an WARN_ON_ONCE here?

No. In normal case, it can come with hkid already reclaimed.

Quoted 105 lines
Quoted 105 lines
arch/x86/kvm/vmx/tdx.c +91 -0
+	if (tdx->tdvpx_pa) {
+		for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++)
+			tdx_reclaim_td_page(tdx->tdvpx_pa[i]);
+		kfree(tdx->tdvpx_pa);
+		tdx->tdvpx_pa = NULL;
+	}
+	tdx_reclaim_td_page(tdx->tdvpr_pa);
+	tdx->tdvpr_pa = 0;
 }
 
 void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
@@ -337,6 +361,8 @@ void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
 	/* TDX doesn't support INIT event. */
 	if (WARN_ON_ONCE(init_event))
 		goto td_bugged;
+	if (WARN_ON_ONCE(is_td_vcpu_created(to_tdx(vcpu))))
+		goto td_bugged;
 
 	/* TDX rquires X2APIC. */
 	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
@@ -791,6 +817,125 @@ int tdx_vm_ioctl(struct kvm *kvm, void __user *argp)
 	return r;
 }
 
+static int tdx_td_vcpu_init(struct kvm_vcpu *vcpu, u64 vcpu_rcx)
+{
+	struct kvm_tdx *kvm_tdx = to_kvm_tdx(vcpu->kvm);
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	unsigned long *tdvpx_pa = NULL;
+	unsigned long tdvpr_pa;
+	unsigned long va;
+	int ret, i;
+	u64 err;
+
+	if (is_td_vcpu_created(tdx))
+		return -EINVAL;
+
+	va = __get_free_page(GFP_KERNEL_ACCOUNT);
+	if (!va)
+		return -ENOMEM;
+	tdvpr_pa = __pa(va);
+
+	tdvpx_pa = kcalloc(tdx_caps.tdvpx_nr_pages, sizeof(*tdx->tdvpx_pa),
+			   GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+	if (!tdvpx_pa) {
+		ret = -ENOMEM;
+		goto free_tdvpr;
+	}
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		va = __get_free_page(GFP_KERNEL_ACCOUNT);
+		if (!va)
+			goto free_tdvpx;
+		tdvpx_pa[i] = __pa(va);
+	}
+
+	err = tdh_vp_create(kvm_tdx->tdr_pa, tdvpr_pa);
+	if (WARN_ON_ONCE(err)) {
+		ret = -EIO;
+		pr_tdx_error(TDH_VP_CREATE, err, NULL);
+		goto td_bugged_free_tdvpx;
+	}
+	tdx->tdvpr_pa = tdvpr_pa;
+
+	tdx->tdvpx_pa = tdvpx_pa;
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		err = tdh_vp_addcx(tdx->tdvpr_pa, tdvpx_pa[i]);
+		if (WARN_ON_ONCE(err)) {
+			ret = -EIO;
+			pr_tdx_error(TDH_VP_ADDCX, err, NULL);
+			for (; i < tdx_caps.tdvpx_nr_pages; i++) {
+				free_page((unsigned long)__va(tdvpx_pa[i]));
+				tdvpx_pa[i] = 0;
+			}
+			goto td_bugged;
+		}
+	}
+
+	err = tdh_vp_init(tdx->tdvpr_pa, vcpu_rcx);
+	if (WARN_ON_ONCE(err)) {
+		ret = -EIO;
+		pr_tdx_error(TDH_VP_INIT, err, NULL);
+		goto td_bugged;
+	}
+
+	vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+
+	return 0;
+
+td_bugged_free_tdvpx:
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		free_page((unsigned long)__va(tdvpx_pa[i]));
+		tdvpx_pa[i] = 0;
+	}
+	kfree(tdvpx_pa);
+td_bugged:
+	vcpu->kvm->vm_bugged = true;
+	return ret;
+
+free_tdvpx:
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++)
+		if (tdvpx_pa[i])
+			free_page((unsigned long)__va(tdvpx_pa[i]));
+	kfree(tdvpx_pa);
+	tdx->tdvpx_pa = NULL;
+free_tdvpr:
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) · line 906

Quoted 6 lines
arch/x86/kvm/vmx/tdx.c +6 -0
+	if (tdvpr_pa)
+		free_page((unsigned long)__va(tdvpr_pa));
+	tdx->tdvpr_pa = 0;
+
+	return ret;
+}

Same comments with using vm_bugged in the previous patch.

I converted it to KVM_BUG_ON().

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

Re: [PATCH v11 024/113] KVM: TDX: Do TDX specific vcpu initialization

Quoted 39 lines · On Tue, 28 Feb 2023 03:17:52 -0800 Isaku Yamahata <[email protected]>…
Quoted 22 lines · Zhi Wang
Quoted 8 lines · On Thu, 12 Jan 2023 08:31:32 -0800 [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TD guest vcpu need to be configured before ready to run which requests addtional information from Device model (e.g. qemu), one 64bit value is passed to vcpu's RCX as an initial value. Repurpose KVM_MEMORY_ENCRYPT_OP to vcpu-scope and add new sub-commands KVM_TDX_INIT_VCPU under it for such additional vcpu configuration.

Better add more details for this mystic value to save the review efforts.

For exmaple, refining the above part as:

----

TD hands-off block(HOB) is used to pass the information from VMM to TD virtual firmware(TDVF). Before KVM calls Intel TDX module to launch TDVF, the address of HOB must be placed in the guest RCX.

Quoted 13 lines

Extend KVM_MEMORY_ENCRYPT_OP to vcpu-scope and add new... so that TDH.VP.INIT can take the address of HOB from QEMU and place it in the guest RCX when initializing a TDX vCPU.

----

The below paragraph seems repeating the end of the first paragraph. Guess it can be refined or removed.

Quoted 3 lines

Add callback for kvm vCPU-scoped operations of KVM_MEMORY_ENCRYPT_OP and add a new subcommand, KVM_TDX_INIT_VCPU, for further vcpu initialization.

Quoted 8 lines

I don't think it's good idea to mention about new terminology HOB and TDVF. We can say, VMM can pass one parameter.
Here is the updated one.

TD guest vcpu needs TDX specific initialization before running. Repurpose KVM_MEMORY_ENCRYPT_OP to vcpu-scope, add a new sub-command KVM_TDX_INIT_VCPU, and implement the callback for it.

Based on the experience of reviewing this patch, I think it depends on:

1) If the reviewer needs to understand the meaning of this parameter to review this patch? If yes, a brief description of what it is (even one sentence) is much better than "one parameter". If no, better mention the pointer, like "Refer XXX spec for more details of xxx". (No need to mention chapter according to Sean's maintainer book).

Direct and informative comment is always helpful for reviewing.

2) If describing this new terminology helps on the reviewing following patches? If the following patches modify logic around this? If yes, should take this opportunity to educate the reviewer with brief descriptions. If no, a pointer is good enough.

Quoted 9 lines
Quoted 8 lines

PS: I am curious if the value of guest RCX on each VCPU will be configured differently? (It seems they are the same according to the code of tdx-qemu)

If yes, then it is just an approach to configure the value (even it is through TDH.VP.XXX). It should be configured in the domain level in KVM. The TDX vCPU creation and initialization can be moved into tdx_vcpu_create() and TDH.VP.INIT can take the value from a per-vm data structure.

Quoted 6 lines

RCX can be set for each VCPUs as ABI (or TDX SEAMCALL API) between VMM and vcpu initial value. It's convention between user space VMM(qemu) and guest firmware(TDVF) to pass same RCX value for all vcpu. So KVM shouldn't enforce same RCX value for all vcpus. KVM should allow user space VMM to set the value for each vcpus.

I see. That makes sense then.

Quoted 239 lines
Quoted 108 lines
Quoted 108 lines
---
 arch/x86/include/asm/kvm-x86-ops.h    |   1 +
 arch/x86/include/asm/kvm_host.h       |   1 +
 arch/x86/include/uapi/asm/kvm.h       |   1 +
 arch/x86/kvm/vmx/main.c               |   9 ++
 arch/x86/kvm/vmx/tdx.c                | 147 +++++++++++++++++++++++++-
 arch/x86/kvm/vmx/tdx.h                |   7 ++
 arch/x86/kvm/vmx/x86_ops.h            |  10 +-
 arch/x86/kvm/x86.c                    |   6 ++
 tools/arch/x86/include/uapi/asm/kvm.h |   1 +
 9 files changed, 178 insertions(+), 5 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 1a27f3aee982..e3e9b1c2599b 100644
@@ -123,6 +123,7 @@ KVM_X86_OP(enable_smi_window)
 #endif
 KVM_X86_OP_OPTIONAL(dev_mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_ioctl)
+KVM_X86_OP_OPTIONAL(vcpu_mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_register_region)
 KVM_X86_OP_OPTIONAL(mem_enc_unregister_region)
 KVM_X86_OP_OPTIONAL(vm_copy_enc_context_from)
arch/x86/include/asm/kvm_host.h +1 -0
index 30f4ddb18548..35773f925cc5 100644
@@ -1698,6 +1698,7 @@ struct kvm_x86_ops {
 
 	int (*dev_mem_enc_ioctl)(void __user *argp);
 	int (*mem_enc_ioctl)(struct kvm *kvm, void __user *argp);
+	int (*vcpu_mem_enc_ioctl)(struct kvm_vcpu *vcpu, void __user *argp);
 	int (*mem_enc_register_region)(struct kvm *kvm, struct kvm_enc_region *argp);
 	int (*mem_enc_unregister_region)(struct kvm *kvm, struct kvm_enc_region *argp);
 	int (*vm_copy_enc_context_from)(struct kvm *kvm, unsigned int source_fd);
arch/x86/include/uapi/asm/kvm.h +1 -0
index b8f28d86d4fd..9236c1699c48 100644
@@ -536,6 +536,7 @@ struct kvm_pmu_event_filter {
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
 	KVM_TDX_INIT_VM,
+	KVM_TDX_INIT_VCPU,
 
 	KVM_TDX_CMD_NR_MAX,
 };
arch/x86/kvm/vmx/main.c +9 -0
index 59813ca05f36..23b3ffc3fe23 100644
@@ -103,6 +103,14 @@ static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 	return tdx_vm_ioctl(kvm, argp);
 }
 
+static int vt_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
+{
+	if (!is_td_vcpu(vcpu))
+		return -EINVAL;
+
+	return tdx_vcpu_ioctl(vcpu, argp);
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
@@ -249,6 +257,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.dev_mem_enc_ioctl = tdx_dev_ioctl,
 	.mem_enc_ioctl = vt_mem_enc_ioctl,
+	.vcpu_mem_enc_ioctl = vt_vcpu_mem_enc_ioctl,
 };
 
 struct kvm_x86_init_ops vt_init_ops __initdata = {
arch/x86/kvm/vmx/tdx.c +10 -0
index 099f0737a5aa..e2f5a07ad4e5 100644
@@ -49,6 +49,11 @@ static __always_inline hpa_t set_hkid_to_hpa(hpa_t pa, u16 hkid)
 	return pa | ((hpa_t)hkid << boot_cpu_data.x86_phys_bits);
 }
 
+static inline bool is_td_vcpu_created(struct vcpu_tdx *tdx)
+{
+	return tdx->tdvpr_pa;
+}
+
 static inline bool is_td_created(struct kvm_tdx *kvm_tdx)
 {
 	return kvm_tdx->tdr_pa;
@@ -65,6 +70,11 @@ static inline bool is_hkid_assigned(struct kvm_tdx *kvm_tdx)
 	return kvm_tdx->hkid > 0;
 }
 
+static inline bool is_td_finalized(struct kvm_tdx *kvm_tdx)
+{
+	return kvm_tdx->finalized;
+}
+
 static void tdx_clear_page(unsigned long page_pa)
 {
 	const void *zero_page = (const void *) __va(page_to_phys(ZERO_PAGE(0)));
@@ -327,7 +337,21 @@ int tdx_vcpu_create(struct kvm_vcpu *vcpu)
 
Quoted 12 lines

arch/x86/kvm/vmx/tdx.c int tdx_vcpu_create(struct kvm_vcpu *vcpu) · line 346

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +7 -1
 void tdx_vcpu_free(struct kvm_vcpu *vcpu)
 {
-	/* This is stub for now.  More logic will come. */
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	int i;
+
+	/* Can't reclaim or free pages if teardown failed. */
+	if (is_hkid_assigned(to_kvm_tdx(vcpu->kvm)))
+		return;
+

Should we have an WARN_ON_ONCE here?

No. In normal case, it can come with hkid already reclaimed.

Quoted 105 lines
Quoted 105 lines
arch/x86/kvm/vmx/tdx.c +91 -0
+	if (tdx->tdvpx_pa) {
+		for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++)
+			tdx_reclaim_td_page(tdx->tdvpx_pa[i]);
+		kfree(tdx->tdvpx_pa);
+		tdx->tdvpx_pa = NULL;
+	}
+	tdx_reclaim_td_page(tdx->tdvpr_pa);
+	tdx->tdvpr_pa = 0;
 }
 
 void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
@@ -337,6 +361,8 @@ void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
 	/* TDX doesn't support INIT event. */
 	if (WARN_ON_ONCE(init_event))
 		goto td_bugged;
+	if (WARN_ON_ONCE(is_td_vcpu_created(to_tdx(vcpu))))
+		goto td_bugged;
 
 	/* TDX rquires X2APIC. */
 	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
@@ -791,6 +817,125 @@ int tdx_vm_ioctl(struct kvm *kvm, void __user *argp)
 	return r;
 }
 
+static int tdx_td_vcpu_init(struct kvm_vcpu *vcpu, u64 vcpu_rcx)
+{
+	struct kvm_tdx *kvm_tdx = to_kvm_tdx(vcpu->kvm);
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	unsigned long *tdvpx_pa = NULL;
+	unsigned long tdvpr_pa;
+	unsigned long va;
+	int ret, i;
+	u64 err;
+
+	if (is_td_vcpu_created(tdx))
+		return -EINVAL;
+
+	va = __get_free_page(GFP_KERNEL_ACCOUNT);
+	if (!va)
+		return -ENOMEM;
+	tdvpr_pa = __pa(va);
+
+	tdvpx_pa = kcalloc(tdx_caps.tdvpx_nr_pages, sizeof(*tdx->tdvpx_pa),
+			   GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+	if (!tdvpx_pa) {
+		ret = -ENOMEM;
+		goto free_tdvpr;
+	}
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		va = __get_free_page(GFP_KERNEL_ACCOUNT);
+		if (!va)
+			goto free_tdvpx;
+		tdvpx_pa[i] = __pa(va);
+	}
+
+	err = tdh_vp_create(kvm_tdx->tdr_pa, tdvpr_pa);
+	if (WARN_ON_ONCE(err)) {
+		ret = -EIO;
+		pr_tdx_error(TDH_VP_CREATE, err, NULL);
+		goto td_bugged_free_tdvpx;
+	}
+	tdx->tdvpr_pa = tdvpr_pa;
+
+	tdx->tdvpx_pa = tdvpx_pa;
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		err = tdh_vp_addcx(tdx->tdvpr_pa, tdvpx_pa[i]);
+		if (WARN_ON_ONCE(err)) {
+			ret = -EIO;
+			pr_tdx_error(TDH_VP_ADDCX, err, NULL);
+			for (; i < tdx_caps.tdvpx_nr_pages; i++) {
+				free_page((unsigned long)__va(tdvpx_pa[i]));
+				tdvpx_pa[i] = 0;
+			}
+			goto td_bugged;
+		}
+	}
+
+	err = tdh_vp_init(tdx->tdvpr_pa, vcpu_rcx);
+	if (WARN_ON_ONCE(err)) {
+		ret = -EIO;
+		pr_tdx_error(TDH_VP_INIT, err, NULL);
+		goto td_bugged;
+	}
+
+	vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+
+	return 0;
+
+td_bugged_free_tdvpx:
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		free_page((unsigned long)__va(tdvpx_pa[i]));
+		tdvpx_pa[i] = 0;
+	}
+	kfree(tdvpx_pa);
+td_bugged:
+	vcpu->kvm->vm_bugged = true;
+	return ret;
+
+free_tdvpx:
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++)
+		if (tdvpx_pa[i])
+			free_page((unsigned long)__va(tdvpx_pa[i]));
+	kfree(tdvpx_pa);
+	tdx->tdvpx_pa = NULL;
+free_tdvpr:
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) · line 906

Quoted 6 lines
arch/x86/kvm/vmx/tdx.c +6 -0
+	if (tdvpr_pa)
+		free_page((unsigned long)__va(tdvpr_pa));
+	tdx->tdvpr_pa = 0;
+
+	return ret;
+}

Same comments with using vm_bugged in the previous patch.

I converted it to KVM_BUG_ON().

#99 lore raw

Re: [PATCH v11 024/113] KVM: TDX: Do TDX specific vcpu initialization

In reply to [email protected]
Quoted 6 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TD guest vcpu need to be configured before ready to run which requests addtional information from Device model (e.g. qemu), one 64bit value is passed to vcpu's RCX as an initial value.

The first half sentence doesn't parse to me. It also has grammar issue.

Also, the second half only talks about TDH.VP.INIT, but there's more regarding to creating/initializing a TDX guest vcpu. IMHO It would be better if you can briefly describe the whole sequence here so people can get some idea about your code below.

Btw, I don't understand what's the point of pointing out "64bit value passed to vcpu's RCX ...". You can add this to the comment instead. If it is important, then please add more to explain it so people can understand more.

Quoted 3 lines

Repurpose KVM_MEMORY_ENCRYPT_OP
to vcpu-scope and add new sub-commands KVM_TDX_INIT_VCPU under it for such additional vcpu configuration.

I am not sure using the same command for both per-VM and per-vcpu ioctls is a good idea. Is there any existing example does this?

Quoted 3 lines

Add callback for kvm vCPU-scoped operations of KVM_MEMORY_ENCRYPT_OP and add a new subcommand, KVM_TDX_INIT_VCPU, for further vcpu initialization.

Personally I prefer KVM_TDX_VCPU_CREATE (instead of INIT) but will leave to maintainers.

Quoted 107 lines
---
 arch/x86/include/asm/kvm-x86-ops.h    |   1 +
 arch/x86/include/asm/kvm_host.h       |   1 +
 arch/x86/include/uapi/asm/kvm.h       |   1 +
 arch/x86/kvm/vmx/main.c               |   9 ++
 arch/x86/kvm/vmx/tdx.c                | 147 +++++++++++++++++++++++++-
 arch/x86/kvm/vmx/tdx.h                |   7 ++
 arch/x86/kvm/vmx/x86_ops.h            |  10 +-
 arch/x86/kvm/x86.c                    |   6 ++
 tools/arch/x86/include/uapi/asm/kvm.h |   1 +
 9 files changed, 178 insertions(+), 5 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 1a27f3aee982..e3e9b1c2599b 100644
@@ -123,6 +123,7 @@ KVM_X86_OP(enable_smi_window)
 #endif
 KVM_X86_OP_OPTIONAL(dev_mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_ioctl)
+KVM_X86_OP_OPTIONAL(vcpu_mem_enc_ioctl)
 KVM_X86_OP_OPTIONAL(mem_enc_register_region)
 KVM_X86_OP_OPTIONAL(mem_enc_unregister_region)
 KVM_X86_OP_OPTIONAL(vm_copy_enc_context_from)
arch/x86/include/asm/kvm_host.h +1 -0
index 30f4ddb18548..35773f925cc5 100644
@@ -1698,6 +1698,7 @@ struct kvm_x86_ops {
 
 	int (*dev_mem_enc_ioctl)(void __user *argp);
 	int (*mem_enc_ioctl)(struct kvm *kvm, void __user *argp);
+	int (*vcpu_mem_enc_ioctl)(struct kvm_vcpu *vcpu, void __user *argp);
 	int (*mem_enc_register_region)(struct kvm *kvm, struct kvm_enc_region *argp);
 	int (*mem_enc_unregister_region)(struct kvm *kvm, struct kvm_enc_region *argp);
 	int (*vm_copy_enc_context_from)(struct kvm *kvm, unsigned int source_fd);
arch/x86/include/uapi/asm/kvm.h +1 -0
index b8f28d86d4fd..9236c1699c48 100644
@@ -536,6 +536,7 @@ struct kvm_pmu_event_filter {
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
 	KVM_TDX_INIT_VM,
+	KVM_TDX_INIT_VCPU,
 
 	KVM_TDX_CMD_NR_MAX,
 };
arch/x86/kvm/vmx/main.c +9 -0
index 59813ca05f36..23b3ffc3fe23 100644
@@ -103,6 +103,14 @@ static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 	return tdx_vm_ioctl(kvm, argp);
 }
 
+static int vt_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
+{
+	if (!is_td_vcpu(vcpu))
+		return -EINVAL;
+
+	return tdx_vcpu_ioctl(vcpu, argp);
+}
+
 struct kvm_x86_ops vt_x86_ops __initdata = {
 	.name = KBUILD_MODNAME,
 
@@ -249,6 +257,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.dev_mem_enc_ioctl = tdx_dev_ioctl,
 	.mem_enc_ioctl = vt_mem_enc_ioctl,
+	.vcpu_mem_enc_ioctl = vt_vcpu_mem_enc_ioctl,
 };
 
 struct kvm_x86_init_ops vt_init_ops __initdata = {
arch/x86/kvm/vmx/tdx.c +10 -0
index 099f0737a5aa..e2f5a07ad4e5 100644
@@ -49,6 +49,11 @@ static __always_inline hpa_t set_hkid_to_hpa(hpa_t pa, u16 hkid)
 	return pa | ((hpa_t)hkid << boot_cpu_data.x86_phys_bits);
 }
 
+static inline bool is_td_vcpu_created(struct vcpu_tdx *tdx)
+{
+	return tdx->tdvpr_pa;
+}
+
 static inline bool is_td_created(struct kvm_tdx *kvm_tdx)
 {
 	return kvm_tdx->tdr_pa;
@@ -65,6 +70,11 @@ static inline bool is_hkid_assigned(struct kvm_tdx *kvm_tdx)
 	return kvm_tdx->hkid > 0;
 }
 
+static inline bool is_td_finalized(struct kvm_tdx *kvm_tdx)
+{
+	return kvm_tdx->finalized;
+}
+
 static void tdx_clear_page(unsigned long page_pa)
 {
 	const void *zero_page = (const void *) __va(page_to_phys(ZERO_PAGE(0)));

arch/x86/kvm/vmx/tdx.c int tdx_vcpu_create(struct kvm_vcpu *vcpu) · line 345

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +6 -1
@@ -327,7 +337,21 @@ int tdx_vcpu_create(struct kvm_vcpu *vcpu)
 
 void tdx_vcpu_free(struct kvm_vcpu *vcpu)
 {
-	/* This is stub for now.  More logic will come. */
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	int i;
+
+	/* Can't reclaim or free pages if teardown failed. */
+	if (is_hkid_assigned(to_kvm_tdx(vcpu->kvm)))
+		return;

You may want to WARN() if it's a kernel bug you want to catch.

Quoted 13 lines
arch/x86/kvm/vmx/tdx.c +9 -0
+
+	if (tdx->tdvpx_pa) {
+		for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++)
+			tdx_reclaim_td_page(tdx->tdvpx_pa[i]);
+		kfree(tdx->tdvpx_pa);
+		tdx->tdvpx_pa = NULL;
+	}
+	tdx_reclaim_td_page(tdx->tdvpr_pa);
+	tdx->tdvpr_pa = 0;
 }
 
 void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event) · line 365

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +2 -0
@@ -337,6 +361,8 @@ void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
 	/* TDX doesn't support INIT event. */
 	if (WARN_ON_ONCE(init_event))
 		goto td_bugged;
+	if (WARN_ON_ONCE(is_td_vcpu_created(to_tdx(vcpu))))
+		goto td_bugged;

Again, not sure can we use KVM_BUG_ON()?

Quoted 9 lines
arch/x86/kvm/vmx/tdx.c +2 -0
 	/* TDX rquires X2APIC. */
 	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
@@ -791,6 +817,125 @@ int tdx_vm_ioctl(struct kvm *kvm, void __user *argp)
 	return r;
 }
 
+static int tdx_td_vcpu_init(struct kvm_vcpu *vcpu, u64 vcpu_rcx)
+{

arch/x86/kvm/vmx/tdx.c int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) · line 831

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	struct kvm_tdx *kvm_tdx = to_kvm_tdx(vcpu->kvm);
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	unsigned long *tdvpx_pa = NULL;
+	unsigned long tdvpr_pa;
+	unsigned long va;
+	int ret, i;
+	u64 err;
+
+	if (is_td_vcpu_created(tdx))
+		return -EINVAL;

Ditto. WARN()?

arch/x86/kvm/vmx/tdx.c int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) · line 839

Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +8 -0
+
+	va = __get_free_page(GFP_KERNEL_ACCOUNT);
+	if (!va)
+		return -ENOMEM;
+	tdvpr_pa = __pa(va);
+
+	tdvpx_pa = kcalloc(tdx_caps.tdvpx_nr_pages, sizeof(*tdx->tdvpx_pa),
+			   GFP_KERNEL_ACCOUNT | __GFP_ZERO);

kcalloc() uses __GFP_ZERO internally.

Quoted 49 lines
arch/x86/kvm/vmx/tdx.c +49 -0
+	if (!tdvpx_pa) {
+		ret = -ENOMEM;
+		goto free_tdvpr;
+	}
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		va = __get_free_page(GFP_KERNEL_ACCOUNT);
+		if (!va)
+			goto free_tdvpx;
+		tdvpx_pa[i] = __pa(va);
+	}
+
+	err = tdh_vp_create(kvm_tdx->tdr_pa, tdvpr_pa);
+	if (WARN_ON_ONCE(err)) {
+		ret = -EIO;
+		pr_tdx_error(TDH_VP_CREATE, err, NULL);
+		goto td_bugged_free_tdvpx;
+	}
+	tdx->tdvpr_pa = tdvpr_pa;
+
+	tdx->tdvpx_pa = tdvpx_pa;
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		err = tdh_vp_addcx(tdx->tdvpr_pa, tdvpx_pa[i]);
+		if (WARN_ON_ONCE(err)) {
+			ret = -EIO;
+			pr_tdx_error(TDH_VP_ADDCX, err, NULL);
+			for (; i < tdx_caps.tdvpx_nr_pages; i++) {
+				free_page((unsigned long)__va(tdvpx_pa[i]));
+				tdvpx_pa[i] = 0;
+			}
+			goto td_bugged;
+		}
+	}
+
+	err = tdh_vp_init(tdx->tdvpr_pa, vcpu_rcx);
+	if (WARN_ON_ONCE(err)) {
+		ret = -EIO;
+		pr_tdx_error(TDH_VP_INIT, err, NULL);
+		goto td_bugged;
+	}
+
+	vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+
+	return 0;
+
+td_bugged_free_tdvpx:
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		free_page((unsigned long)__va(tdvpx_pa[i]));
+		tdvpx_pa[i] = 0;
+	}

arch/x86/kvm/vmx/tdx.c int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) · line 898

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	kfree(tdvpx_pa);
+td_bugged:
+	vcpu->kvm->vm_bugged = true;
+	return ret;
+
+free_tdvpx:
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++)
+		if (tdvpx_pa[i])
+			free_page((unsigned long)__va(tdvpx_pa[i]));
+	kfree(tdvpx_pa);

This piece of code appears 3 times in this function (and there are 3 'return ret;'). I am sure it can be done in one place instead. Can you reorganize?

Quoted 53 lines
arch/x86/kvm/vmx/tdx.c +40 -0
+	tdx->tdvpx_pa = NULL;
+free_tdvpr:
+	if (tdvpr_pa)
+		free_page((unsigned long)__va(tdvpr_pa));
+	tdx->tdvpr_pa = 0;
+
+	return ret;
+}
+
+int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
+{
+	struct kvm_tdx *kvm_tdx = to_kvm_tdx(vcpu->kvm);
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	struct kvm_tdx_cmd cmd;
+	int ret;
+
+	if (tdx->vcpu_initialized)
+		return -EINVAL;
+
+	if (!is_hkid_assigned(kvm_tdx) || is_td_finalized(kvm_tdx))
+		return -EINVAL;
+
+	if (copy_from_user(&cmd, argp, sizeof(cmd)))
+		return -EFAULT;
+
+	if (cmd.error || cmd.unused)
+		return -EINVAL;
+
+	/* Currently only KVM_TDX_INTI_VCPU is defined for vcpu operation. */
+	if (cmd.flags || cmd.id != KVM_TDX_INIT_VCPU)
+		return -EINVAL;
+
+	ret = tdx_td_vcpu_init(vcpu, (u64)cmd.data);
+	if (ret)
+		return ret;
+
+	tdx->vcpu_initialized = true;
+	return 0;
+}
+
 static int __init tdx_module_setup(void)
 {
 	const struct tdsysinfo_struct *tdsysinfo;
arch/x86/kvm/vmx/tdx.h +2 -0
index af7fdc1516d5..e909883d60fa 100644
@@ -17,12 +17,19 @@ struct kvm_tdx {
 	u64 xfam;
 	int hkid;
 
+	bool finalized;
+

arch/x86/kvm/vmx/tdx.h struct kvm_tdx { · line 31

Quoted 10 lines
arch/x86/kvm/vmx/tdx.h +4 -0
 	u64 tsc_offset;
 };
 
 struct vcpu_tdx {
 	struct kvm_vcpu	vcpu;
 
+	unsigned long tdvpr_pa;
+	unsigned long *tdvpx_pa;
+
+	bool vcpu_initialized;

The 'vcpu_' prefix is kinda redundant.

Quoted 10 lines
arch/x86/kvm/vmx/tdx.h +1 -0
+
 	/*
 	 * Dummy to make pmu_intel not corrupt memory.
 	 * TODO: Support PMU for TDX.  Future work.
arch/x86/kvm/vmx/x86_ops.h +0 -0
index 37ab2cfd35bc..fba8d0800597 100644
@@ -148,11 +148,12 @@ int tdx_vm_init(struct kvm *kvm);
 void tdx_mmu_release_hkid(struct kvm *kvm);

arch/x86/kvm/vmx/x86_ops.h int tdx_vm_init(struct kvm *kvm); · line 156

Quoted 10 lines
arch/x86/kvm/vmx/x86_ops.h +3 -2
 void tdx_vm_free(struct kvm *kvm);
 
-int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
-
 int tdx_vcpu_create(struct kvm_vcpu *vcpu);
 void tdx_vcpu_free(struct kvm_vcpu *vcpu);
 void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event);
+
+int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);
+int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp);

Why bother moving the tdx_vm_ioctl() declaration?

Quoted 47 lines
arch/x86/kvm/vmx/x86_ops.h +3 -2
 #else
 static inline int tdx_hardware_setup(struct kvm_x86_ops *x86_ops) { return 0; }
 static inline void tdx_hardware_unsetup(void) {}
@@ -165,11 +166,12 @@ static inline void tdx_mmu_release_hkid(struct kvm *kvm) {}
 static inline void tdx_flush_shadow_all_private(struct kvm *kvm) {}
 static inline void tdx_vm_free(struct kvm *kvm) {}
 
-static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
-
 static inline int tdx_vcpu_create(struct kvm_vcpu *vcpu) { return -EOPNOTSUPP; }
 static inline void tdx_vcpu_free(struct kvm_vcpu *vcpu) {}
 static inline void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event) {}
+
+static inline int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
+static inline int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp) { return -EOPNOTSUPP; }
 #endif
 
 #endif /* __KVM_X86_VMX_X86_OPS_H */
arch/x86/kvm/x86.c +6 -0
index e8bc66031a1d..d548d3af6428 100644
@@ -5976,6 +5976,12 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
 	case KVM_SET_DEVICE_ATTR:
 		r = kvm_vcpu_ioctl_device_attr(vcpu, ioctl, argp);
 		break;
+	case KVM_MEMORY_ENCRYPT_OP:
+		r = -ENOTTY;
+		if (!kvm_x86_ops.vcpu_mem_enc_ioctl)
+			goto out;
+		r = kvm_x86_ops.vcpu_mem_enc_ioctl(vcpu, argp);
+		break;
 	default:
 		r = -EINVAL;
 	}
tools/arch/x86/include/uapi/asm/kvm.h +1 -0
index eb800965b589..6971f1288043 100644
@@ -531,6 +531,7 @@ struct kvm_pmu_event_filter {
 enum kvm_tdx_cmd_id {
 	KVM_TDX_CAPABILITIES = 0,
 	KVM_TDX_INIT_VM,
+	KVM_TDX_INIT_VCPU,
 
 	KVM_TDX_CMD_NR_MAX,
 };
#100 lore raw

Re: [PATCH v11 024/113] KVM: TDX: Do TDX specific vcpu initialization

Quoted 10 lines · Kai
Quoted 6 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TD guest vcpu need to be configured before ready to run which requests addtional information from Device model (e.g. qemu), one 64bit value is passed to vcpu's RCX as an initial value.

The first half sentence doesn't parse to me. It also has grammar issue.

Quoted 9 lines

Also, the second half only talks about TDH.VP.INIT, but there's more regarding to creating/initializing a TDX guest vcpu. IMHO It would be better if you can briefly describe the whole sequence here so people can get some idea about your code below.

Btw, I don't understand what's the point of pointing out "64bit value passed to vcpu's RCX ...". You can add this to the comment instead. If it is important, then please add more to explain it so people can understand more.

RCX and 64bit value doesn't make much sense in the commit message. I dropped those sentence.

Here is the updated commit message.

KVM: TDX: Do TDX specific vcpu initialization

TD guest vcpu needs TDX specific initialization before running. Repurpose KVM_MEMORY_ENCRYPT_OP to vcpu-scope, add a new sub-command KVM_TDX_INIT_VCPU, and implement the callback for it.

Quoted 6 lines
Quoted 3 lines

Repurpose KVM_MEMORY_ENCRYPT_OP
to vcpu-scope and add new sub-commands KVM_TDX_INIT_VCPU under it for such additional vcpu configuration.

I am not sure using the same command for both per-VM and per-vcpu ioctls is a good idea. Is there any existing example does this?

There are some. Please break Documentation/virt/kvm/api.rst with "type:". You can see some ioctl supports multiple type.

Type: system ioctl, vm ioctl
Type: vcpu ioctl / vm ioctl
Type: device ioctl, vm ioctl, vcpu ioctl

Quoted 30 lines
Quoted 30 lines
arch/x86/kvm/vmx/tdx.c +10 -0
index 099f0737a5aa..e2f5a07ad4e5 100644
@@ -49,6 +49,11 @@ static __always_inline hpa_t set_hkid_to_hpa(hpa_t pa, u16 hkid)
 	return pa | ((hpa_t)hkid << boot_cpu_data.x86_phys_bits);
 }
 
+static inline bool is_td_vcpu_created(struct vcpu_tdx *tdx)
+{
+	return tdx->tdvpr_pa;
+}
+
 static inline bool is_td_created(struct kvm_tdx *kvm_tdx)
 {
 	return kvm_tdx->tdr_pa;
@@ -65,6 +70,11 @@ static inline bool is_hkid_assigned(struct kvm_tdx *kvm_tdx)
 	return kvm_tdx->hkid > 0;
 }
 
+static inline bool is_td_finalized(struct kvm_tdx *kvm_tdx)
+{
+	return kvm_tdx->finalized;
+}
+
 static void tdx_clear_page(unsigned long page_pa)
 {
 	const void *zero_page = (const void *) __va(page_to_phys(ZERO_PAGE(0)));
@@ -327,7 +337,21 @@ int tdx_vcpu_create(struct kvm_vcpu *vcpu)
 
Quoted 11 lines

arch/x86/kvm/vmx/tdx.c int tdx_vcpu_create(struct kvm_vcpu *vcpu) · line 345

Quoted 9 lines
arch/x86/kvm/vmx/tdx.c +6 -1
 void tdx_vcpu_free(struct kvm_vcpu *vcpu)
 {
-	/* This is stub for now.  More logic will come. */
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	int i;
+
+	/* Can't reclaim or free pages if teardown failed. */
+	if (is_hkid_assigned(to_kvm_tdx(vcpu->kvm)))
+		return;

You may want to WARN() if it's a kernel bug you want to catch.

No, it's not a bug to come here with hkid freed because vcpus_free method can be called after vm destruction.

Quoted 11 lines
Quoted 11 lines
arch/x86/kvm/vmx/tdx.c +9 -0
+
+	if (tdx->tdvpx_pa) {
+		for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++)
+			tdx_reclaim_td_page(tdx->tdvpx_pa[i]);
+		kfree(tdx->tdvpx_pa);
+		tdx->tdvpx_pa = NULL;
+	}
+	tdx_reclaim_td_page(tdx->tdvpr_pa);
+	tdx->tdvpr_pa = 0;
 }
 
Quoted 9 lines

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event) · line 365

Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +2 -0
 void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
@@ -337,6 +361,8 @@ void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
 	/* TDX doesn't support INIT event. */
 	if (WARN_ON_ONCE(init_event))
 		goto td_bugged;
+	if (WARN_ON_ONCE(is_td_vcpu_created(to_tdx(vcpu))))
+		goto td_bugged;

Again, not sure can we use KVM_BUG_ON()?

I converted it into KVM_BUG_ON()

Quoted 6 lines
Quoted 6 lines
arch/x86/kvm/vmx/tdx.c +0 -0
 	/* TDX rquires X2APIC. */
 	apic_base_msr.data = APIC_DEFAULT_PHYS_BASE | LAPIC_MODE_X2APIC;
@@ -791,6 +817,125 @@ int tdx_vm_ioctl(struct kvm *kvm, void __user *argp)
 	return r;
 }
 
Quoted 14 lines

arch/x86/kvm/vmx/tdx.c int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) · line 831

Quoted 12 lines
arch/x86/kvm/vmx/tdx.c +12 -0
+static int tdx_td_vcpu_init(struct kvm_vcpu *vcpu, u64 vcpu_rcx)
+{
+	struct kvm_tdx *kvm_tdx = to_kvm_tdx(vcpu->kvm);
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+	unsigned long *tdvpx_pa = NULL;
+	unsigned long tdvpr_pa;
+	unsigned long va;
+	int ret, i;
+	u64 err;
+
+	if (is_td_vcpu_created(tdx))
+		return -EINVAL;

Ditto. WARN()?

No. KVM_TDX_INIT_VCPU can be called multiple times. It's not kernel bug, but misuse of the ioctl.

Quoted 65 lines

arch/x86/kvm/vmx/tdx.c int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) · line 839

Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +8 -0
+
+	va = __get_free_page(GFP_KERNEL_ACCOUNT);
+	if (!va)
+		return -ENOMEM;
+	tdvpr_pa = __pa(va);
+
+	tdvpx_pa = kcalloc(tdx_caps.tdvpx_nr_pages, sizeof(*tdx->tdvpx_pa),
+			   GFP_KERNEL_ACCOUNT | __GFP_ZERO);

kcalloc() uses __GFP_ZERO internally.

Quoted 54 lines
arch/x86/kvm/vmx/tdx.c +54 -0
+	if (!tdvpx_pa) {
+		ret = -ENOMEM;
+		goto free_tdvpr;
+	}
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		va = __get_free_page(GFP_KERNEL_ACCOUNT);
+		if (!va)
+			goto free_tdvpx;
+		tdvpx_pa[i] = __pa(va);
+	}
+
+	err = tdh_vp_create(kvm_tdx->tdr_pa, tdvpr_pa);
+	if (WARN_ON_ONCE(err)) {
+		ret = -EIO;
+		pr_tdx_error(TDH_VP_CREATE, err, NULL);
+		goto td_bugged_free_tdvpx;
+	}
+	tdx->tdvpr_pa = tdvpr_pa;
+
+	tdx->tdvpx_pa = tdvpx_pa;
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		err = tdh_vp_addcx(tdx->tdvpr_pa, tdvpx_pa[i]);
+		if (WARN_ON_ONCE(err)) {
+			ret = -EIO;
+			pr_tdx_error(TDH_VP_ADDCX, err, NULL);
+			for (; i < tdx_caps.tdvpx_nr_pages; i++) {
+				free_page((unsigned long)__va(tdvpx_pa[i]));
+				tdvpx_pa[i] = 0;
+			}
+			goto td_bugged;
+		}
+	}
+
+	err = tdh_vp_init(tdx->tdvpr_pa, vcpu_rcx);
+	if (WARN_ON_ONCE(err)) {
+		ret = -EIO;
+		pr_tdx_error(TDH_VP_INIT, err, NULL);
+		goto td_bugged;
+	}
+
+	vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+
+	return 0;
+
+td_bugged_free_tdvpx:
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++) {
+		free_page((unsigned long)__va(tdvpx_pa[i]));
+		tdvpx_pa[i] = 0;
+	}
+	kfree(tdvpx_pa);
+td_bugged:
+	vcpu->kvm->vm_bugged = true;
+	return ret;
+
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) · line 898

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+free_tdvpx:
+	for (i = 0; i < tdx_caps.tdvpx_nr_pages; i++)
+		if (tdvpx_pa[i])
+			free_page((unsigned long)__va(tdvpx_pa[i]));
+	kfree(tdvpx_pa);

This piece of code appears 3 times in this function (and there are 3 'return ret;'). I am sure it can be done in one place instead. Can you reorganize?

I improved the logic to delete the duplication.

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

PATCH v11 25/113 KVM: TDX: Use private memory for TDX

In reply to [email protected]

From: Chao Peng <[email protected]>

Override kvm_arch_has_private_mem() to use fd-based private memory. Return true when a VM has a type of KVM_X86_TDX_VM.

---
 arch/x86/kvm/x86.c | 5 +++++
 1 file changed, 5 insertions(+)
arch/x86/kvm/x86.c +5 -0
index d548d3af6428..a8b555935fd8 100644
@@ -13498,6 +13498,11 @@ int kvm_sev_es_string_io(struct kvm_vcpu *vcpu, unsigned int size,
 }
 EXPORT_SYMBOL_GPL(kvm_sev_es_string_io);
 
+bool kvm_arch_has_private_mem(struct kvm *kvm)
+{
+	return kvm->arch.vm_type == KVM_X86_TDX_VM;
+}
+
 EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_entry);
 EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_exit);
 EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_fast_mmio);
Signature · 1 line
2.25.1
#102 lore raw

Re: [PATCH v11 025/113] KVM: TDX: Use private memory for TDX

Quoted 16 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Chao Peng <[email protected]>

Override kvm_arch_has_private_mem() to use fd-based private memory. Return true when a VM has a type of KVM_X86_TDX_VM.

---
 arch/x86/kvm/x86.c | 5 +++++
 1 file changed, 5 insertions(+)
arch/x86/kvm/x86.c +0 -0
index d548d3af6428..a8b555935fd8 100644

arch/x86/kvm/x86.c int kvm_sev_es_string_io(struct kvm_vcpu *vcpu, unsigned int size, · line 13505

Quoted 8 lines
arch/x86/kvm/x86.c +5 -0
@@ -13498,6 +13498,11 @@ int kvm_sev_es_string_io(struct kvm_vcpu *vcpu, unsigned int size,
 }
 EXPORT_SYMBOL_GPL(kvm_sev_es_string_io);
 
+bool kvm_arch_has_private_mem(struct kvm *kvm)
+{
+	return kvm->arch.vm_type == KVM_X86_TDX_VM;
+}
+

AMD's series has a different solution:

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

I think somehow this needs to get aligned.

Simply checking whether VM is TD certainly won't work for AMD, so my first glance we should use something similar to AMD's solution. But I haven't thought thoroughly that is absolutely needed, for instance, perhaps we can use 'gfn_shared_mask', etc.

#103 lore raw

Re: [PATCH v11 025/113] KVM: TDX: Use private memory for TDX

Quoted 28 lines · Kai
Quoted 16 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Chao Peng <[email protected]>

Override kvm_arch_has_private_mem() to use fd-based private memory. Return true when a VM has a type of KVM_X86_TDX_VM.

---
 arch/x86/kvm/x86.c | 5 +++++
 1 file changed, 5 insertions(+)
arch/x86/kvm/x86.c +0 -0
index d548d3af6428..a8b555935fd8 100644

arch/x86/kvm/x86.c int kvm_sev_es_string_io(struct kvm_vcpu *vcpu, unsigned int size, · line 13505

Quoted 8 lines
arch/x86/kvm/x86.c +5 -0
@@ -13498,6 +13498,11 @@ int kvm_sev_es_string_io(struct kvm_vcpu *vcpu, unsigned int size,
 }
 EXPORT_SYMBOL_GPL(kvm_sev_es_string_io);
 
+bool kvm_arch_has_private_mem(struct kvm *kvm)
+{
+	return kvm->arch.vm_type == KVM_X86_TDX_VM;
+}
+

AMD's series has a different solution:

Quoted 3 lines

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

I think somehow this needs to get aligned.

Ya. My thought is

bool kvm_arch_has_private_mem(struct kvm *kvm)

 {
	return kvm->arch.vm_type != KVM_X86_DEFAULT_VM;

}

where the VM types end up being:

#define KVM_X86_DEFAULT_VM 0
#define KVM_X86_PROTECTED_VM 1
#define KVM_X86_TDX_VM 2
#define KVM_X86_SNP_VM 3

Don't spend too much time reworking the TDX series at this point, I'm going to do a trial run of combining UPM+TDX+SNP sometime in the next few weeks to see how all the pieces fit together, this is one of the common touchpoints that I'll make sure to look at. Though if you have ideas on, by all means post them.

#104 lore raw

Re: [PATCH v11 025/113] KVM: TDX: Use private memory for TDX

Quoted 38 lines · Sean Christopherson
Quoted 28 lines · Kai
Quoted 16 lines · On Thu, 2023-01-12 at 08:31 -0800, [email protected] wrote:

From: Chao Peng <[email protected]>

Override kvm_arch_has_private_mem() to use fd-based private memory. Return true when a VM has a type of KVM_X86_TDX_VM.

---
 arch/x86/kvm/x86.c | 5 +++++
 1 file changed, 5 insertions(+)
arch/x86/kvm/x86.c +0 -0
index d548d3af6428..a8b555935fd8 100644

arch/x86/kvm/x86.c int kvm_sev_es_string_io(struct kvm_vcpu *vcpu, unsigned int size, · line 13505

Quoted 8 lines
arch/x86/kvm/x86.c +5 -0
@@ -13498,6 +13498,11 @@ int kvm_sev_es_string_io(struct kvm_vcpu *vcpu, unsigned int size,
 }
 EXPORT_SYMBOL_GPL(kvm_sev_es_string_io);
 
+bool kvm_arch_has_private_mem(struct kvm *kvm)
+{
+	return kvm->arch.vm_type == KVM_X86_TDX_VM;
+}
+

AMD's series has a different solution:

Quoted 3 lines

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

I think somehow this needs to get aligned.

Ya. My thought is

bool kvm_arch_has_private_mem(struct kvm *kvm)

 {
	return kvm->arch.vm_type != KVM_X86_DEFAULT_VM;
Quoted 8 lines

}

where the VM types end up being:

#define KVM_X86_DEFAULT_VM 0
#define KVM_X86_PROTECTED_VM 1
#define KVM_X86_TDX_VM 2
#define KVM_X86_SNP_VM 3

What's the difference between PROTECTED_VM vs TDX_VM/SNP_VM, may I ask?

Quoted 5 lines

Don't spend too much time reworking the TDX series at this point, I'm going to do a trial run of combining UPM+TDX+SNP sometime in the next few weeks to see how all the pieces fit together, this is one of the common touchpoints that I'll make sure to look at. Though if you have ideas on, by all means post them.

Glad to know. Thanks for your time.

#105 lore raw

Re: [PATCH v11 025/113] KVM: TDX: Use private memory for TDX

Quoted 15 lines · Kai
Quoted 14 lines · Sean Christopherson
Quoted 5 lines · Kai

AMD's series has a different solution:

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

I think somehow this needs to get aligned.

Ya. My thought is

bool kvm_arch_has_private_mem(struct kvm *kvm)

 {
	return kvm->arch.vm_type != KVM_X86_DEFAULT_VM;

}

Quoted 8 lines
Quoted 6 lines

where the VM types end up being:

#define KVM_X86_DEFAULT_VM 0
#define KVM_X86_PROTECTED_VM 1
#define KVM_X86_TDX_VM 2
#define KVM_X86_SNP_VM 3

What's the difference between PROTECTED_VM vs TDX_VM/SNP_VM, may I ask?

Not sure :-)

At this point, PROTECTED_VM is a handwavy "guest that can access restricted memory". I think/hope it can be used in combination with existing SEV/SEV-ES APIs to allow backing such guests with restrictedmem.

As for TDX_VM and SNP_VM, I'm speculating that KVM will need to differentiate between SNP, TDX, and everything else. E.g. if KVM needs to uniquely identify TDX VMs at creation time, then adding dedicated VM types seems like the obvious choice. Ditto for SNP VMs.

That's partly why I want to smush everything together, to see if we actually need multiple VM types.

#106 lore raw

PATCH v11 26/113 The start of TDX KVM patch series: KVM MMU GPA shared bits

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

This empty commit is to mark the start of patch series of KVM MMU GPA shared bits.

---
 Documentation/virt/kvm/intel-tdx-layer-status.rst | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
Documentation/virt/kvm/intel-tdx-layer-status.rst +3 -2
index a4ee04271d66..88343749d4c2 100644
@@ -10,6 +10,7 @@ What qemu can do
 ----------------
 - TDX VM TYPE is exposed to Qemu.
 - Qemu can create/destroy guest of TDX vm type.
+- Qemu can create/destroy vcpu of TDX vm type.
 
 Patch Layer status
 ------------------
@@ -17,12 +18,12 @@ Patch Layer status
 * TDX, VMX coexistence:                 Applied
 * TDX architectural definitions:        Applied
 * TD VM creation/destruction:           Applied
-* TD vcpu creation/destruction:         Applying
+* TD vcpu creation/destruction:         Applied
 * TDX EPT violation:                    Not yet
 * TD finalization:                      Not yet
 * TD vcpu enter/exit:                   Not yet
 * TD vcpu interrupts/exit/hypercall:    Not yet
 
-* KVM MMU GPA shared bits:              Not yet
+* KVM MMU GPA shared bits:              Applying
 * KVM TDP refactoring for TDX:          Not yet
 * KVM TDP MMU hooks:                    Not yet
Signature · 1 line
2.25.1
#107 lore raw

PATCH v11 27/113 KVM: x86/mmu: introduce config for PRIVATE KVM MMU

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

To keep the case of non TDX intact, introduce a new config option for private KVM MMU support. At the moment, this is synonym for CONFIG_INTEL_TDX_HOST && CONFIG_KVM_INTEL. The config makes it clear that the config is only for x86 KVM MMU.

---
 arch/x86/kvm/Kconfig | 4 ++++
 1 file changed, 4 insertions(+)
arch/x86/kvm/Kconfig +4 -0
index 84d21ef8b3d2..a2db2169ea9c 100644
@@ -145,4 +145,8 @@ config KVM_XEN
 config KVM_EXTERNAL_WRITE_TRACKING
 	bool
 
+config KVM_MMU_PRIVATE
+	def_bool y
+	depends on INTEL_TDX_HOST && KVM_INTEL
+
 endif # VIRTUALIZATION
Signature · 1 line
2.25.1
#108 lore raw

PATCH v11 28/113 KVM: x86/mmu: Add address conversion functions for TDX shared bit of GPA

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TDX repurposes one GPA bit (51 bit or 47 bit based on configuration) to indicate the GPA is private(if cleared) or shared (if set) with VMM. If GPA.shared is set, GPA is covered by the existing conventional EPT pointed by EPTP. If GPA.shared bit is cleared, GPA is covered by TDX module. VMM has to issue SEAMCALLs to operate.

Add a member to remember GPA shared bit for each guest TDs, add address conversion functions between private GPA and shared GPA and test if GPA is private.

Because struct kvm_arch (or struct kvm which includes struct kvm_arch. See kvm_arch_alloc_vm() that passes __GPF_ZERO) is zero-cleared when allocated, the new member to remember GPA shared bit is guaranteed to be zero with this patch unless it's initialized explicitly.

---
 arch/x86/include/asm/kvm_host.h |  4 ++++
 arch/x86/kvm/mmu.h              | 32 ++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/tdx.c          |  5 +++++
 3 files changed, 41 insertions(+)
arch/x86/include/asm/kvm_host.h +4 -0
index 35773f925cc5..73c987b3d2b6 100644
@@ -1439,6 +1439,10 @@ struct kvm_arch {
 	 */
 #define SPLIT_DESC_CACHE_MIN_NR_OBJECTS (SPTE_ENT_PER_PAGE + 1)
 	struct kvm_mmu_memory_cache split_desc_cache;
+
+#ifdef CONFIG_KVM_MMU_PRIVATE
+	gfn_t gfn_shared_mask;
+#endif
 };
 
 struct kvm_vm_stat {
arch/x86/kvm/mmu.h +32 -0
index 6bdaacb6faa0..a45f7a96b821 100644
@@ -278,4 +278,36 @@ static inline gpa_t kvm_translate_gpa(struct kvm_vcpu *vcpu,
 		return gpa;
 	return translate_nested_gpa(vcpu, gpa, access, exception);
 }
+
+static inline gfn_t kvm_gfn_shared_mask(const struct kvm *kvm)
+{
+#ifdef CONFIG_KVM_MMU_PRIVATE
+	return kvm->arch.gfn_shared_mask;
+#else
+	return 0;
+#endif
+}
+
+static inline gfn_t kvm_gfn_shared(const struct kvm *kvm, gfn_t gfn)
+{
+	return gfn | kvm_gfn_shared_mask(kvm);
+}
+
+static inline gfn_t kvm_gfn_private(const struct kvm *kvm, gfn_t gfn)
+{
+	return gfn & ~kvm_gfn_shared_mask(kvm);
+}
+
+static inline gpa_t kvm_gpa_private(const struct kvm *kvm, gpa_t gpa)
+{
+	return gpa & ~gfn_to_gpa(kvm_gfn_shared_mask(kvm));
+}
+
+static inline bool kvm_is_private_gpa(const struct kvm *kvm, gpa_t gpa)
+{
+	gfn_t mask = kvm_gfn_shared_mask(kvm);
+
+	return mask && !(gpa_to_gfn(gpa) & mask);
+}
+
 #endif
arch/x86/kvm/vmx/tdx.c +5 -0
index e2f5a07ad4e5..a7d42c05a758 100644
@@ -781,6 +781,11 @@ static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd)
 	kvm_tdx->attributes = td_params->attributes;
 	kvm_tdx->xfam = td_params->xfam;
 
+	if (td_params->exec_controls & TDX_EXEC_CONTROL_MAX_GPAW)
+		kvm->arch.gfn_shared_mask = gpa_to_gfn(BIT_ULL(51));
+	else
+		kvm->arch.gfn_shared_mask = gpa_to_gfn(BIT_ULL(47));
+
 out:
 	/* kfree() accepts NULL. */
 	kfree(init_vm);
Signature · 1 line
2.25.1
#109 lore raw

PATCH v11 29/113 The start of TDX KVM patch series: KVM TDP refactoring for TDX

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

This empty commit is to mark the start of patch series of KVM TDP refactoring for TDX.

---
 Documentation/virt/kvm/intel-tdx-layer-status.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Documentation/virt/kvm/intel-tdx-layer-status.rst +2 -2
index 88343749d4c2..f10aff0b060e 100644
@@ -24,6 +24,6 @@ Patch Layer status
 * TD vcpu enter/exit:                   Not yet
 * TD vcpu interrupts/exit/hypercall:    Not yet
 
-* KVM MMU GPA shared bits:              Applying
-* KVM TDP refactoring for TDX:          Not yet
+* KVM MMU GPA shared bits:              Applied
+* KVM TDP refactoring for TDX:          Applying
 * KVM TDP MMU hooks:                    Not yet
Signature · 1 line
2.25.1