[RFC PATCH 05/55] KVM: x86: hyper-v: Add KVM_CAP_HYPERV_FORWARD_HCALL

From: David Woodhouse

Date: Sat Oct 17 2026 - 08:38:54 EST


From: David Woodhouse <dwmw@xxxxxxxxxxxx>

A VMM implementing Hyper-V hypercalls that KVM does not know about,
such as the VSM set, has no way to receive them: kvm_hv_hypercall()
fails unknown codes with HV_STATUS_INVALID_HYPERCALL_CODE, and only
syndbg and slow-form extended hypercalls ever exit to userspace.

Add a VM capability which routes any hypercall KVM would have failed
as unknown out through the existing KVM_EXIT_HYPERV_HCALL exit, fast
forms included. The exit already carries the control value and both
parameters (RDX and R8 for fast calls), and XMM state is reachable
through the ordinary vCPU ioctls, so no new exit format is needed.
A future in-kernel accelerator for a specific hypercall can still
claim its code behind its own capability, with userspace handling as
the fallback.

Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
Assisted-by: LLM
---
Documentation/virt/kvm/api.rst | 26 ++++++++++++++++++++++++++
arch/x86/include/asm/kvm_host.h | 1 +
arch/x86/kvm/hyperv.c | 5 ++++-
arch/x86/kvm/x86.c | 10 ++++++++++
include/uapi/linux/kvm.h | 1 +
5 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index bc63054ce7bd..ade8c91ef24b 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -8986,6 +8986,32 @@ This capability must be enabled before any vCPU or extra plane has been
created, and the chosen count cannot be changed afterwards; otherwise
-EEXIST is returned.

+7.48 KVM_CAP_HYPERV_FORWARD_HCALL
+---------------------------------
+
+:Architectures: x86
+:Target: VM
+:Parameters: args[0] is 1 to enable forwarding, 0 to disable it
+
+When enabled, a Hyper-V hypercall that KVM would otherwise have failed
+with HV_STATUS_INVALID_HYPERCALL_CODE instead exits to userspace with
+KVM_EXIT_HYPERV_HCALL, and extended hypercalls in fast form exit the
+same way instead of failing with HV_STATUS_INVALID_PARAMETER. This
+lets a VMM implement hypercalls KVM does not know, such as the Hyper-V
+VSM set. The synthetic-debugger hypercalls (HvCallResetDebugSession,
+HvCallPostDebugData, HvCallRetrieveDebugData) are an exception: while
+the synthetic debugger is not enabled they still fail in KVM with
+HV_STATUS_INVALID_HYPERCALL_CODE and are not forwarded.
+
+The exit carries the control value and the input and output parameters
+(for fast hypercalls, RDX and R8 from a 64-bit caller, or the EBX:ECX
+and EDI:ESI pairs from a 32-bit caller). Register argument and
+return state of fast hypercalls, such as the XMM registers, is read and
+written with the ordinary vCPU state ioctls before resuming the vCPU
+with ``hyperv.u.hcall.result`` set. Hypercalls KVM implements itself,
+and the ordering of CPUID-enforcement checks against hypercall
+dispatch, are unaffected.
+
7.49 KVM_CAP_HCALL_FAULT_EXIT
------------------------------------

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index e4c8691dc1e1..05670b5ae28a 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1204,6 +1204,7 @@ struct kvm_hv {
struct mutex hv_lock;
u64 hv_guest_os_id;
u64 hv_hypercall;
+ bool hv_forward_hcalls;
u64 hv_tsc_page;
enum hv_tsc_page_status hv_tsc_page_status;

diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 98a3c2aefdcd..ec669dafda6e 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -2777,12 +2777,15 @@ int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
return kvm_hv_hypercall_userspace_exit(vcpu, &hc);
}
case HV_EXT_CALL_QUERY_CAPABILITIES ... HV_EXT_CALL_MAX:
- if (unlikely(hc.fast)) {
+ if (unlikely(hc.fast) &&
+ !to_kvm_hv(vcpu->kvm)->hv_forward_hcalls) {
ret = HV_STATUS_INVALID_PARAMETER;
break;
}
return kvm_hv_hypercall_userspace_exit(vcpu, &hc);
default:
+ if (to_kvm_hv(vcpu->kvm)->hv_forward_hcalls)
+ return kvm_hv_hypercall_userspace_exit(vcpu, &hc);
ret = HV_STATUS_INVALID_HYPERCALL_CODE;
break;
}
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index a9073436c734..8de596e00441 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -4883,6 +4883,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_HYPERV_CPUID:
case KVM_CAP_HYPERV_ENFORCE_CPUID:
case KVM_CAP_SYS_HYPERV_CPUID:
+ case KVM_CAP_HYPERV_FORWARD_HCALL:
#endif
case KVM_CAP_HCALL_FAULT_EXIT:
case KVM_CAP_PCI_SEGMENT:
@@ -6886,6 +6887,15 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
mutex_unlock(&kvm->lock);
break;
}
+#ifdef CONFIG_KVM_HYPERV
+ case KVM_CAP_HYPERV_FORWARD_HCALL:
+ r = -EINVAL;
+ if (cap->args[0] & ~1ULL)
+ break;
+ to_kvm_hv(kvm)->hv_forward_hcalls = !!cap->args[0];
+ r = 0;
+ break;
+#endif
case KVM_CAP_X2APIC_API:
r = -EINVAL;
if (cap->args[0] & ~KVM_X2APIC_API_VALID_FLAGS)
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 839e9e83d21d..5567cf352907 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1015,6 +1015,7 @@ struct kvm_enable_cap {
#define KVM_CAP_S390_KEYOP 247
#define KVM_CAP_S390_VSIE_ESAMODE 248
#define KVM_CAP_PLANES 249
+#define KVM_CAP_HYPERV_FORWARD_HCALL 250
#define KVM_CAP_HCALL_FAULT_EXIT 252

struct kvm_irq_routing_irqchip {
--
2.50.1