Skip to content

Commit 2c30488

Browse files
Binbin Wubonzini
authored andcommitted
KVM: TDX: Handle TDG.VP.VMCALL<MapGPA>
Convert TDG.VP.VMCALL<MapGPA> to KVM_EXIT_HYPERCALL with KVM_HC_MAP_GPA_RANGE and forward it to userspace for handling. MapGPA is used by TDX guest to request to map a GPA range as private or shared memory. It needs to exit to userspace for handling. KVM has already implemented a similar hypercall KVM_HC_MAP_GPA_RANGE, which will exit to userspace with exit reason KVM_EXIT_HYPERCALL. Do sanity checks, convert TDVMCALL_MAP_GPA to KVM_HC_MAP_GPA_RANGE and forward the request to userspace. To prevent a TDG.VP.VMCALL<MapGPA> call from taking too long, the MapGPA range is split into 2MB chunks and check interrupt pending between chunks. This allows for timely injection of interrupts and prevents issues with guest lockup detection. TDX guest should retry the operation for the GPA starting at the address specified in R11 when the TDVMCALL return TDVMCALL_RETRY as status code. Note userspace needs to enable KVM_CAP_EXIT_HYPERCALL with KVM_HC_MAP_GPA_RANGE bit set for TD VM. Suggested-by: Sean Christopherson <seanjc@google.com> Signed-off-by: Binbin Wu <binbin.wu@linux.intel.com> Message-ID: <20250222014225.897298-7-binbin.wu@linux.intel.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent d5998c0 commit 2c30488

File tree

3 files changed

+115
-0
lines changed

3 files changed

+115
-0
lines changed

arch/x86/include/asm/shared/tdx.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
#define TDVMCALL_STATUS_SUCCESS 0x0000000000000000ULL
7878
#define TDVMCALL_STATUS_RETRY 0x0000000000000001ULL
7979
#define TDVMCALL_STATUS_INVALID_OPERAND 0x8000000000000000ULL
80+
#define TDVMCALL_STATUS_ALIGN_ERROR 0x8000000000000002ULL
8081

8182
/*
8283
* Bitmasks of exposed registers (with VMM).

arch/x86/kvm/vmx/tdx.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,9 +995,120 @@ static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu)
995995
return __kvm_emulate_hypercall(vcpu, 0, complete_hypercall_exit);
996996
}
997997

998+
/*
999+
* Split into chunks and check interrupt pending between chunks. This allows
1000+
* for timely injection of interrupts to prevent issues with guest lockup
1001+
* detection.
1002+
*/
1003+
#define TDX_MAP_GPA_MAX_LEN (2 * 1024 * 1024)
1004+
static void __tdx_map_gpa(struct vcpu_tdx *tdx);
1005+
1006+
static int tdx_complete_vmcall_map_gpa(struct kvm_vcpu *vcpu)
1007+
{
1008+
struct vcpu_tdx *tdx = to_tdx(vcpu);
1009+
1010+
if (vcpu->run->hypercall.ret) {
1011+
tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
1012+
tdx->vp_enter_args.r11 = tdx->map_gpa_next;
1013+
return 1;
1014+
}
1015+
1016+
tdx->map_gpa_next += TDX_MAP_GPA_MAX_LEN;
1017+
if (tdx->map_gpa_next >= tdx->map_gpa_end)
1018+
return 1;
1019+
1020+
/*
1021+
* Stop processing the remaining part if there is a pending interrupt,
1022+
* which could be qualified to deliver. Skip checking pending RVI for
1023+
* TDVMCALL_MAP_GPA.
1024+
* TODO: Add a comment to link the reason when the target function is
1025+
* implemented.
1026+
*/
1027+
if (kvm_vcpu_has_events(vcpu)) {
1028+
tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
1029+
tdx->vp_enter_args.r11 = tdx->map_gpa_next;
1030+
return 1;
1031+
}
1032+
1033+
__tdx_map_gpa(tdx);
1034+
return 0;
1035+
}
1036+
1037+
static void __tdx_map_gpa(struct vcpu_tdx *tdx)
1038+
{
1039+
u64 gpa = tdx->map_gpa_next;
1040+
u64 size = tdx->map_gpa_end - tdx->map_gpa_next;
1041+
1042+
if (size > TDX_MAP_GPA_MAX_LEN)
1043+
size = TDX_MAP_GPA_MAX_LEN;
1044+
1045+
tdx->vcpu.run->exit_reason = KVM_EXIT_HYPERCALL;
1046+
tdx->vcpu.run->hypercall.nr = KVM_HC_MAP_GPA_RANGE;
1047+
/*
1048+
* In principle this should have been -KVM_ENOSYS, but userspace (QEMU <=9.2)
1049+
* assumed that vcpu->run->hypercall.ret is never changed by KVM and thus that
1050+
* it was always zero on KVM_EXIT_HYPERCALL. Since KVM is now overwriting
1051+
* vcpu->run->hypercall.ret, ensuring that it is zero to not break QEMU.
1052+
*/
1053+
tdx->vcpu.run->hypercall.ret = 0;
1054+
tdx->vcpu.run->hypercall.args[0] = gpa & ~gfn_to_gpa(kvm_gfn_direct_bits(tdx->vcpu.kvm));
1055+
tdx->vcpu.run->hypercall.args[1] = size / PAGE_SIZE;
1056+
tdx->vcpu.run->hypercall.args[2] = vt_is_tdx_private_gpa(tdx->vcpu.kvm, gpa) ?
1057+
KVM_MAP_GPA_RANGE_ENCRYPTED :
1058+
KVM_MAP_GPA_RANGE_DECRYPTED;
1059+
tdx->vcpu.run->hypercall.flags = KVM_EXIT_HYPERCALL_LONG_MODE;
1060+
1061+
tdx->vcpu.arch.complete_userspace_io = tdx_complete_vmcall_map_gpa;
1062+
}
1063+
1064+
static int tdx_map_gpa(struct kvm_vcpu *vcpu)
1065+
{
1066+
struct vcpu_tdx *tdx = to_tdx(vcpu);
1067+
u64 gpa = tdx->vp_enter_args.r12;
1068+
u64 size = tdx->vp_enter_args.r13;
1069+
u64 ret;
1070+
1071+
/*
1072+
* Converting TDVMCALL_MAP_GPA to KVM_HC_MAP_GPA_RANGE requires
1073+
* userspace to enable KVM_CAP_EXIT_HYPERCALL with KVM_HC_MAP_GPA_RANGE
1074+
* bit set. If not, the error code is not defined in GHCI for TDX, use
1075+
* TDVMCALL_STATUS_INVALID_OPERAND for this case.
1076+
*/
1077+
if (!user_exit_on_hypercall(vcpu->kvm, KVM_HC_MAP_GPA_RANGE)) {
1078+
ret = TDVMCALL_STATUS_INVALID_OPERAND;
1079+
goto error;
1080+
}
1081+
1082+
if (gpa + size <= gpa || !kvm_vcpu_is_legal_gpa(vcpu, gpa) ||
1083+
!kvm_vcpu_is_legal_gpa(vcpu, gpa + size - 1) ||
1084+
(vt_is_tdx_private_gpa(vcpu->kvm, gpa) !=
1085+
vt_is_tdx_private_gpa(vcpu->kvm, gpa + size - 1))) {
1086+
ret = TDVMCALL_STATUS_INVALID_OPERAND;
1087+
goto error;
1088+
}
1089+
1090+
if (!PAGE_ALIGNED(gpa) || !PAGE_ALIGNED(size)) {
1091+
ret = TDVMCALL_STATUS_ALIGN_ERROR;
1092+
goto error;
1093+
}
1094+
1095+
tdx->map_gpa_end = gpa + size;
1096+
tdx->map_gpa_next = gpa;
1097+
1098+
__tdx_map_gpa(tdx);
1099+
return 0;
1100+
1101+
error:
1102+
tdvmcall_set_return_code(vcpu, ret);
1103+
tdx->vp_enter_args.r11 = gpa;
1104+
return 1;
1105+
}
1106+
9981107
static int handle_tdvmcall(struct kvm_vcpu *vcpu)
9991108
{
10001109
switch (tdvmcall_leaf(vcpu)) {
1110+
case TDVMCALL_MAP_GPA:
1111+
return tdx_map_gpa(vcpu);
10011112
default:
10021113
break;
10031114
}

arch/x86/kvm/vmx/tdx.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ struct vcpu_tdx {
6060

6161
enum vcpu_tdx_state state;
6262
bool guest_entered;
63+
64+
u64 map_gpa_next;
65+
u64 map_gpa_end;
6366
};
6467

6568
void tdh_vp_rd_failed(struct vcpu_tdx *tdx, char *uclass, u32 field, u64 err);

0 commit comments

Comments
 (0)