From: David Woodhouse <[email protected]> A VMM doing VSM in userspace has to switch the executing plane when the guest asks for a privilege transition, but a vCPU's plane run state is kernel-internal — set only by the SEV-SNP VMPL switch, plane-0 event delivery, and vCPU creation. A VTL call forwarded to userspace is a dead end.
Add ioctls to read and write the run state as stopped/runnable. KVM_RUN already executes the lowest-numbered runnable plane, so a userspace plane switch is just: mark the target plane's vCPU runnable, mark the current one stopped, re-enter KVM_RUN. A plane 0 stopped this way *stays* stopped. Forcing it runnable on a pending event fits the SEV-SNP model, but under a userspace privilege model plane 0 would preempt the higher VTL on any interrupt and resume mid-hypercall with whatever completion value the shared kvm_run last held; the events stay pending until the VMM switches back. In-kernel transitions never set the stopped-by-userspace flag, so SNP behaviour is unchanged. The ioctls return -EINVAL unless kvm_arch_max_planes() reports plane support, the same condition KVM_CREATE_PLANE enforces: only x86 consumes KVM_REQ_PLANE_RESCHED, and on the other architectures a stray request bit would abort every subsequent entry attempt, leaving KVM_RUN spinning at 100% with no way for the VMM to recover the vCPU. Signed-off-by: David Woodhouse <[email protected]> Assisted-by: LLM --- Documentation/virt/kvm/api.rst | 26 ++++++++++++++++++++ arch/x86/kvm/x86.c | 3 ++- include/linux/kvm_host.h | 6 +++++ include/uapi/linux/kvm.h | 4 ++++ virt/kvm/kvm_main.c | 44 ++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 1 deletion(-) diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index ade8c91ef24b..f8d9bca1baea 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -6611,6 +6611,32 @@ in the future may use the plane id when planes are supported by hardware (as is the case for VMPLs in AMD), or if KVM supports accelerated plane switch operations (as might be the case for Hyper-V VTLs). +4.146 KVM_GET_PLANE_RUN_STATE / KVM_SET_PLANE_RUN_STATE +-------------------------------------------------------- + +:Capability: KVM_CAP_PLANES +:Architectures: x86 +:Type: vcpu ioctl +:Parameters: pointer to a __u32 run state +:Returns: 0 on success, <0 on error + +Reads or sets the per-plane run state of a vCPU: +KVM_PLANE_RUN_STATE_STOPPED (0) or KVM_PLANE_RUN_STATE_RUNNABLE (1). + +When a vCPU thread calls KVM_RUN, KVM executes the lowest-numbered plane +whose vCPU is runnable, falling back to plane 0. A VMM implementing a +software privilege model (such as Hyper-V VSM) switches planes by +marking the target plane's vCPU runnable and the current one stopped +before re-entering KVM_RUN. Pending events for plane 0 make it runnable +again on their own only as long as userspace has not explicitly stopped +it with this ioctl; a VMM-stopped plane 0 keeps its events pending until +the VMM makes it runnable, so a software privilege model can keep a +higher plane running undisturbed. The ioctl is accepted on any plane's +vCPU fd. + +The run state is distinct from KVM_GET/SET_MP_STATE, which describes +the architectural state of a vCPU within its plane. + 5. The kvm_run structure ======================== diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 8de596e00441..ca5d8e081fdc 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -11508,7 +11508,8 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) } } - if (kvm_check_plane0_events(vcpu)) { + if (kvm_check_plane0_events(vcpu) && + !vcpu->common->vcpus[0]->plane_user_stopped) { kvm_vcpu_set_plane_runnable(vcpu->common->vcpus[0]); kvm_make_request(KVM_REQ_EVENT, vcpu); diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 7ed79479b4a1..5bdae2127578 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -413,6 +413,12 @@ struct kvm_vcpu { u64 plane_requests; enum kvm_vcpu_state plane_state; + /* + * Set when userspace stopped this plane with + * KVM_SET_PLANE_RUN_STATE: pending plane-0 events then no longer + * make plane 0 runnable on their own, the VMM owns the switch. + */ + bool plane_user_stopped; /* S390 only */ bool valid_wakeup; diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 5567cf352907..2aeb8c5172cb 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -1371,6 +1371,10 @@ struct kvm_s390_keyop { #define KVM_HAS_DEVICE_ATTR _IOW(KVMIO, 0xe3, struct kvm_device_attr) #define KVM_CREATE_PLANE _IO(KVMIO, 0xe4) +#define KVM_GET_PLANE_RUN_STATE _IOR(KVMIO, 0xe5, __u32) +#define KVM_SET_PLANE_RUN_STATE _IOW(KVMIO, 0xe6, __u32) +#define KVM_PLANE_RUN_STATE_STOPPED 0 +#define KVM_PLANE_RUN_STATE_RUNNABLE 1 /* * ioctls for vcpu fds diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index c8b0722ac35c..2d2dc7828e1f 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -4788,6 +4788,8 @@ static inline bool kvm_is_vcpu_plane_ioctl(unsigned ioctl) case KVM_GET_SREGS: case KVM_SET_SREGS: case KVM_TRANSLATE: + case KVM_GET_PLANE_RUN_STATE: + case KVM_SET_PLANE_RUN_STATE: return true; default: return kvm_arch_is_vcpu_plane_ioctl(ioctl); @@ -4928,6 +4930,48 @@ static long kvm_vcpu_ioctl(struct file *filp, r = kvm_arch_vcpu_ioctl_set_sregs(vcpu, kvm_sregs); break; } + case KVM_GET_PLANE_RUN_STATE: { + u32 state = vcpu->plane_state == RUNNABLE ? + KVM_PLANE_RUN_STATE_RUNNABLE : + KVM_PLANE_RUN_STATE_STOPPED; + + r = -EINVAL; + if (kvm_arch_max_planes(vcpu->kvm) <= 1) + goto out; + r = -EFAULT; + if (copy_to_user(argp, &state, sizeof(state))) + goto out; + r = 0; + break; + } + case KVM_SET_PLANE_RUN_STATE: { + u32 state; + + /* + * KVM_SET_PLANE_RUN_STATE raises KVM_REQ_PLANE_RESCHED, + * which only plane-aware architecture code consumes; on a + * planes-less VM the stuck request would abort every guest + * entry and spin KVM_RUN at 100%. + */ + r = -EINVAL; + if (kvm_arch_max_planes(vcpu->kvm) <= 1) + goto out; + r = -EFAULT; + if (copy_from_user(&state, argp, sizeof(state))) + goto out; + r = -EINVAL; + if (state > KVM_PLANE_RUN_STATE_RUNNABLE) + goto out; + if (state == KVM_PLANE_RUN_STATE_RUNNABLE) { + vcpu->plane_user_stopped = false; + kvm_vcpu_set_plane_runnable(vcpu); + } else { + vcpu->plane_user_stopped = true; + kvm_vcpu_set_plane_stopped(vcpu); + } + r = 0; + break; + } case KVM_GET_MP_STATE: { struct kvm_mp_state mp_state; -- 2.50.1

