KVM: TDX: Handle TDG.VP.VMCALL<GetQuote>

Handle TDVMCALL for GetQuote to generate a TD-Quote.

GetQuote is a doorbell-like interface used by TDX guests to request VMM
to generate a TD-Quote signed by a service hosting TD-Quoting Enclave
operating on the host. A TDX guest passes a TD Report (TDREPORT_STRUCT) in
a shared-memory area as parameter. Host VMM can access it and queue the
operation for a service hosting TD-Quoting enclave. When completed, the
Quote is returned via the same shared-memory area.

KVM only checks the GPA from the TDX guest has the shared-bit set and drops
the shared-bit before exiting to userspace to avoid bleeding the shared-bit
into KVM's exit ABI. KVM forwards the request to userspace VMM (e.g. QEMU)
and userspace VMM queues the operation asynchronously. KVM sets the return
code according to the 'ret' field set by userspace to notify the TDX guest
whether the request has been queued successfully or not. When the request
has been queued successfully, the TDX guest can poll the status field in
the shared-memory area to check whether the Quote generation is completed
or not. When completed, the generated Quote is returned via the same
buffer.

Add KVM_EXIT_TDX as a new exit reason to userspace. Userspace is
required to handle the KVM exit reason as the initial support for TDX,
by reentering KVM to ensure that the TDVMCALL is complete. While at it,
add a note that KVM_EXIT_HYPERCALL also requires reentry with KVM_RUN.

Signed-off-by: Binbin Wu <binbin.wu@linux.intel.com>
Tested-by: Mikko Ylinen <mikko.ylinen@linux.intel.com>
Acked-by: Kai Huang <kai.huang@intel.com>
[Adjust userspace API. - Paolo]
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

authored by Binbin Wu and committed by Paolo Bonzini cf207eac b5aafcb4

+97 -1
+48 -1
Documentation/virt/kvm/api.rst
··· 6645 .. note:: 6646 6647 For KVM_EXIT_IO, KVM_EXIT_MMIO, KVM_EXIT_OSI, KVM_EXIT_PAPR, KVM_EXIT_XEN, 6648 - KVM_EXIT_EPR, KVM_EXIT_X86_RDMSR and KVM_EXIT_X86_WRMSR the corresponding 6649 operations are complete (and guest state is consistent) only after userspace 6650 has re-entered the kernel with KVM_RUN. The kernel side will first finish 6651 incomplete operations and then check for pending signals. ··· 7175 - KVM_NOTIFY_CONTEXT_INVALID -- the VM context is corrupted and not valid 7176 in VMCS. It would run into unknown result if resume the target VM. 7177 7178 :: 7179 7180 /* Fix the size of the union. */
··· 6645 .. note:: 6646 6647 For KVM_EXIT_IO, KVM_EXIT_MMIO, KVM_EXIT_OSI, KVM_EXIT_PAPR, KVM_EXIT_XEN, 6648 + KVM_EXIT_EPR, KVM_EXIT_HYPERCALL, KVM_EXIT_TDX, 6649 + KVM_EXIT_X86_RDMSR and KVM_EXIT_X86_WRMSR the corresponding 6650 operations are complete (and guest state is consistent) only after userspace 6651 has re-entered the kernel with KVM_RUN. The kernel side will first finish 6652 incomplete operations and then check for pending signals. ··· 7174 - KVM_NOTIFY_CONTEXT_INVALID -- the VM context is corrupted and not valid 7175 in VMCS. It would run into unknown result if resume the target VM. 7176 7177 + :: 7178 + 7179 + /* KVM_EXIT_TDX */ 7180 + struct { 7181 + __u64 flags; 7182 + __u64 nr; 7183 + union { 7184 + struct { 7185 + u64 ret; 7186 + u64 data[5]; 7187 + } unknown; 7188 + struct { 7189 + u64 ret; 7190 + u64 gpa; 7191 + u64 size; 7192 + } get_quote; 7193 + }; 7194 + } tdx; 7195 + 7196 + Process a TDVMCALL from the guest. KVM forwards select TDVMCALL based 7197 + on the Guest-Hypervisor Communication Interface (GHCI) specification; 7198 + KVM bridges these requests to the userspace VMM with minimal changes, 7199 + placing the inputs in the union and copying them back to the guest 7200 + on re-entry. 7201 + 7202 + Flags are currently always zero, whereas ``nr`` contains the TDVMCALL 7203 + number from register R11. The remaining field of the union provide the 7204 + inputs and outputs of the TDVMCALL. Currently the following values of 7205 + ``nr`` are defined: 7206 + 7207 + * ``TDVMCALL_GET_QUOTE``: the guest has requested to generate a TD-Quote 7208 + signed by a service hosting TD-Quoting Enclave operating on the host. 7209 + Parameters and return value are in the ``get_quote`` field of the union. 7210 + The ``gpa`` field and ``size`` specify the guest physical address 7211 + (without the shared bit set) and the size of a shared-memory buffer, in 7212 + which the TDX guest passes a TD Report. The ``ret`` field represents 7213 + the return value of the GetQuote request. When the request has been 7214 + queued successfully, the TDX guest can poll the status field in the 7215 + shared-memory area to check whether the Quote generation is completed or 7216 + not. When completed, the generated Quote is returned via the same buffer. 7217 + 7218 + KVM may add support for more values in the future that may cause a userspace 7219 + exit, even without calls to ``KVM_ENABLE_CAP`` or similar. In this case, 7220 + it will enter with output fields already valid; in the common case, the 7221 + ``unknown.ret`` field of the union will be ``TDVMCALL_STATUS_SUBFUNC_UNSUPPORTED``. 7222 + Userspace need not do anything if it does not wish to support a TDVMCALL. 7223 :: 7224 7225 /* Fix the size of the union. */
+32
arch/x86/kvm/vmx/tdx.c
··· 1465 return 1; 1466 } 1467 1468 static int handle_tdvmcall(struct kvm_vcpu *vcpu) 1469 { 1470 switch (tdvmcall_leaf(vcpu)) { ··· 1504 return tdx_report_fatal_error(vcpu); 1505 case TDVMCALL_GET_TD_VM_CALL_INFO: 1506 return tdx_get_td_vm_call_info(vcpu); 1507 default: 1508 break; 1509 }
··· 1465 return 1; 1466 } 1467 1468 + static int tdx_complete_simple(struct kvm_vcpu *vcpu) 1469 + { 1470 + tdvmcall_set_return_code(vcpu, vcpu->run->tdx.unknown.ret); 1471 + return 1; 1472 + } 1473 + 1474 + static int tdx_get_quote(struct kvm_vcpu *vcpu) 1475 + { 1476 + struct vcpu_tdx *tdx = to_tdx(vcpu); 1477 + u64 gpa = tdx->vp_enter_args.r12; 1478 + u64 size = tdx->vp_enter_args.r13; 1479 + 1480 + /* The gpa of buffer must have shared bit set. */ 1481 + if (vt_is_tdx_private_gpa(vcpu->kvm, gpa)) { 1482 + tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND); 1483 + return 1; 1484 + } 1485 + 1486 + vcpu->run->exit_reason = KVM_EXIT_TDX; 1487 + vcpu->run->tdx.flags = 0; 1488 + vcpu->run->tdx.nr = TDVMCALL_GET_QUOTE; 1489 + vcpu->run->tdx.get_quote.ret = TDVMCALL_STATUS_SUBFUNC_UNSUPPORTED; 1490 + vcpu->run->tdx.get_quote.gpa = gpa & ~gfn_to_gpa(kvm_gfn_direct_bits(tdx->vcpu.kvm)); 1491 + vcpu->run->tdx.get_quote.size = size; 1492 + 1493 + vcpu->arch.complete_userspace_io = tdx_complete_simple; 1494 + 1495 + return 0; 1496 + } 1497 + 1498 static int handle_tdvmcall(struct kvm_vcpu *vcpu) 1499 { 1500 switch (tdvmcall_leaf(vcpu)) { ··· 1474 return tdx_report_fatal_error(vcpu); 1475 case TDVMCALL_GET_TD_VM_CALL_INFO: 1476 return tdx_get_td_vm_call_info(vcpu); 1477 + case TDVMCALL_GET_QUOTE: 1478 + return tdx_get_quote(vcpu); 1479 default: 1480 break; 1481 }
+17
include/uapi/linux/kvm.h
··· 178 #define KVM_EXIT_NOTIFY 37 179 #define KVM_EXIT_LOONGARCH_IOCSR 38 180 #define KVM_EXIT_MEMORY_FAULT 39 181 182 /* For KVM_EXIT_INTERNAL_ERROR */ 183 /* Emulate instruction failed. */ ··· 448 __u64 gpa; 449 __u64 size; 450 } memory_fault; 451 /* Fix the size of the union. */ 452 char padding[256]; 453 };
··· 178 #define KVM_EXIT_NOTIFY 37 179 #define KVM_EXIT_LOONGARCH_IOCSR 38 180 #define KVM_EXIT_MEMORY_FAULT 39 181 + #define KVM_EXIT_TDX 40 182 183 /* For KVM_EXIT_INTERNAL_ERROR */ 184 /* Emulate instruction failed. */ ··· 447 __u64 gpa; 448 __u64 size; 449 } memory_fault; 450 + /* KVM_EXIT_TDX */ 451 + struct { 452 + __u64 flags; 453 + __u64 nr; 454 + union { 455 + struct { 456 + __u64 ret; 457 + __u64 data[5]; 458 + } unknown; 459 + struct { 460 + __u64 ret; 461 + __u64 gpa; 462 + __u64 size; 463 + } get_quote; 464 + }; 465 + } tdx; 466 /* Fix the size of the union. */ 467 char padding[256]; 468 };