Index | Thread | Search

From:
Hans-Jörg Höxer <hshoexer@genua.de>
Subject:
Re: [EXT] Re: SEV-ES: vmm(4): Prepare handler for VMGEXIT
To:
<tech@openbsd.org>
Date:
Wed, 21 May 2025 12:07:45 +0200

Download raw body.

Thread
Hi,

On Tue, May 20, 2025 at 05:50:07PM -0700, Mike Larkin wrote:
> On Tue, May 20, 2025 at 11:53:28AM +0200, Hans-Jörg Höxer wrote:
> > Hi,
> >
> > this diff prepares the VMGEXIT handler.  Right now it is a stub that
> > will be filled with dispatchers for the actual exit reasons (eg.
> > SVM_VMEXIT_CPUID):
> >
> >     SEV-ES enabled guests will convert non-automatic VM exits to VMGEXIT.
> >     The information needed, to handle the exit reason are provided by
> >     the guest in the GHCB.
> >
> >     For example, when the guest needs to emulate the CPUID instruction,
> >     it will proivded the function value in %eax and the actual exit
> >     reason (SVM_VMEXIT_CPUID) in the GHCB.  And then the guest will
> >     switch back to vmm(4) using the VMGEXIT call.
> >
> >     In vmm(4) svm_handle_gexit() will then sync the values provided in
> >     the GHCB with state information in struct vcpu and struct vmcb.
> >     Then it will emulate CPUID as usual.  After sychronizing the results
> >     back to the GHCB the guest can be resumed.
> >
> >     For now provide a stub handler for SVM_VMEXIT_VMGEXIT and functions
> >     for synchronizing GHCB with struct vcpu and struct vmcb.  The
> >     switch-case statements in the stubs will error out.
> >
> > Take care.
> > HJ.
> 
> One minor request below otherwise ok mlarkin. Thanks.
> 
> -ml
> 
> > ---------------------------------------------------------------------------
> > commit 30f9fa610f786227f7ed5bd9bf209f4f5d9f64df
> > Author: Hans-Joerg Hoexer <hshoexer@genua.de>
> > Date:   Tue Aug 13 11:39:36 2024 +0200
> >
> >     vmm(4): Prepare handler for VMGEXIT
> >
> >     SEV-ES enabled guests will convert non-automatic VM exits to VMGEXIT.
> >     The information needed, to handle the exit reason are provided by
> >     the guest in the GHCB.
> >
> >     For example, when the guest needs to emulate the CPUID instruction,
> >     it will proivded the function value in %eax and the actual exit
> >     reason (SVM_VMEXIT_CPUID) in the GHCB.  And then the guest will
> >     switch back to vmm(4) using the VMGEXIT call.
> >
> >     In vmm(4) svm_handle_gexit() will then sync the values provided in
> >     the GHCB with state information in struct vcpu and struct vmcb.
> >     Then it will emulate CPUID as usual.  After sychronizing the results
> >     back to the GHCB the guest can be resumed.
> >
> >     For now provide a stub handler for SVM_VMEXIT_VMGEXIT and functions
> >     for synchronizing GHCB with struct vcpu and struct vmcb.  The
> >     switch-case statements in the stubs will error out.
> >
> > diff --git a/sys/arch/amd64/amd64/vmm_machdep.c b/sys/arch/amd64/amd64/vmm_machdep.c
> > index 9470222c351..f654a6d0054 100644
> > --- a/sys/arch/amd64/amd64/vmm_machdep.c
> > +++ b/sys/arch/amd64/amd64/vmm_machdep.c
> > @@ -38,6 +38,7 @@
> >  #include <machine/segments.h>
> >  #include <machine/cpu.h>
> >  #include <machine/cpufunc.h>
> > +#include <machine/ghcb.h>
> >  #include <machine/vmmvar.h>
> >
> >  #include <dev/isa/isareg.h>
> > @@ -97,6 +98,9 @@ int vmx_get_exit_info(uint64_t *, uint64_t *);
> >  int vmx_load_pdptes(struct vcpu *);
> >  int vmx_handle_exit(struct vcpu *);
> >  int svm_handle_exit(struct vcpu *);
> > +int svm_gexit_sync_host(struct vcpu *);
> > +int svm_gexit_sync_guest(struct vcpu *);
> > +int svm_handle_gexit(struct vcpu *);
> >  int svm_handle_efercr(struct vcpu *, uint64_t);
> >  int svm_get_iflag(struct vcpu *, uint64_t);
> >  int svm_handle_msr(struct vcpu *);
> > @@ -4316,6 +4320,9 @@ svm_handle_exit(struct vcpu *vcpu)
> >  		ret = svm_handle_efercr(vcpu, exit_reason);
> >  		update_rip = 0;
> >  		break;
> > +	case SVM_VMEXIT_VMGEXIT:
> > +		ret = svm_handle_gexit(vcpu);
> 
> can we name this function svm_handle_vmgexit to keep the naming consistent?

sure!  Updated diff below.  I also adjusted for vm->vm_pmap.

> I already got confused once while reading this because we have a similarly
> named "svm_handle_exit" and I thought for a moment you were redoing that
> function. Just want to avoid confusion in the future.
> 
> > +		break;
> >  	default:
> >  		DPRINTF("%s: unhandled exit 0x%llx (pa=0x%llx)\n", __func__,
> >  		    exit_reason, (uint64_t)vcpu->vc_control_pa);
> > @@ -4341,6 +4348,158 @@ svm_handle_exit(struct vcpu *vcpu)
> >  	return (ret);
> >  }
> >
> > +/*
> > + * sync guest ghcb -> host vmcb/vcpu
> > + */
> > +int
> > +svm_gexit_sync_host(struct vcpu *vcpu)
> > +{
> > +	struct vmcb		*vmcb = (struct vmcb *)vcpu->vc_control_va;
> > +	struct ghcb_sa		*ghcb;
> > +	uint64_t		 svm_sw_exitcode;
> > +	uint8_t			*valid_bm, expected_bm[0x10];
> > +
> > +	if (!vcpu->vc_seves)
> > +		return (0);
> > +
> > +	if (vcpu->vc_svm_ghcb_va == 0)
> > +		return (0);
> > +	ghcb = (struct ghcb_sa *)vcpu->vc_svm_ghcb_va;
> > +
> > +	if (!ghcb_valid(ghcb))
> > +		return (EINVAL);
> > +	valid_bm = ghcb->valid_bitmap;
> > +
> > +	/* Always required. */
> > +	memset(expected_bm, 0, sizeof(expected_bm));
> > +	ghcb_valbm_set(expected_bm, GHCB_SW_EXITCODE);
> > +	ghcb_valbm_set(expected_bm, GHCB_SW_EXITINFO1);
> > +	ghcb_valbm_set(expected_bm, GHCB_SW_EXITINFO2);
> > +
> > +	svm_sw_exitcode = ghcb->v_sw_exitcode;
> > +	switch (svm_sw_exitcode) {
> > +	default:
> > +		return (EINVAL);
> > +	}
> > +
> > +	if (ghcb_verify_bm(valid_bm, expected_bm) != 0)
> > +		return (EINVAL);
> > +
> > +	/* Always required */
> > +	vmcb->v_exitcode = vcpu->vc_gueststate.vg_exit_reason =
> > +	    ghcb->v_sw_exitcode;
> > +	vmcb->v_exitinfo1 = ghcb->v_sw_exitinfo1;
> > +	vmcb->v_exitinfo2 = ghcb->v_sw_exitinfo2;
> > +
> > +	if (ghcb_valbm_isset(expected_bm, GHCB_RAX))
> > +		vmcb->v_rax = vcpu->vc_gueststate.vg_rax = ghcb->v_rax;
> > +	if (ghcb_valbm_isset(expected_bm, GHCB_RBX))
> > +		vcpu->vc_gueststate.vg_rbx = ghcb->v_rbx;
> > +	if (ghcb_valbm_isset(expected_bm, GHCB_RCX))
> > +		vcpu->vc_gueststate.vg_rcx = ghcb->v_rcx;
> > +	if (ghcb_valbm_isset(expected_bm, GHCB_RDX))
> > +		vcpu->vc_gueststate.vg_rdx = ghcb->v_rdx;
> > +
> > +	return (0);
> > +}
> > +
> > +/*
> > + * sync host vmcb/vcpu -> guest ghcb
> > + */
> > +int
> > +svm_gexit_sync_guest(struct vcpu *vcpu)
> > +{
> > +	uint64_t		 svm_sw_exitcode;
> > +	uint64_t		 svm_sw_exitinfo1, svm_sw_exitinfo2;
> > +	uint8_t			*valid_bm;
> > +	struct ghcb_sa		*ghcb;
> > +
> > +	if (!vcpu->vc_seves)
> > +		return (0);
> > +
> > +	if (vcpu->vc_svm_ghcb_va == 0)
> > +		return (0);
> > +
> > +	ghcb = (struct ghcb_sa *)vcpu->vc_svm_ghcb_va;
> > +	svm_sw_exitcode = ghcb->v_sw_exitcode;
> > +	svm_sw_exitinfo1 = ghcb->v_sw_exitinfo1;
> > +	svm_sw_exitinfo2 = ghcb->v_sw_exitinfo2;
> > +	ghcb_clear(ghcb);
> > +	valid_bm = ghcb->valid_bitmap;
> > +
> > +	switch (svm_sw_exitcode) {
> > +	default:
> > +		return (EINVAL);
> > +	}
> > +
> > +	/* Always required */
> > +	svm_sw_exitinfo1 = 0;
> > +	svm_sw_exitinfo2 = 0;
> > +	ghcb_valbm_set(valid_bm, GHCB_SW_EXITINFO1);
> > +	ghcb_valbm_set(valid_bm, GHCB_SW_EXITINFO2);
> > +
> > +	if (ghcb_valbm_isset(valid_bm, GHCB_RAX))
> > +		ghcb->v_rax = vcpu->vc_gueststate.vg_rax;
> > +	if (ghcb_valbm_isset(valid_bm, GHCB_RBX))
> > +		ghcb->v_rbx = vcpu->vc_gueststate.vg_rbx;
> > +	if (ghcb_valbm_isset(valid_bm, GHCB_RCX))
> > +		ghcb->v_rcx = vcpu->vc_gueststate.vg_rcx;
> > +	if (ghcb_valbm_isset(valid_bm, GHCB_RDX))
> > +		ghcb->v_rdx = vcpu->vc_gueststate.vg_rdx;
> > +
> > +	if (ghcb_valbm_isset(valid_bm, GHCB_SW_EXITINFO1))
> > +		ghcb->v_sw_exitinfo1 = svm_sw_exitinfo1;
> > +	if (ghcb_valbm_isset(valid_bm, GHCB_SW_EXITINFO2))
> > +		ghcb->v_sw_exitinfo2 = svm_sw_exitinfo2;
> > +
> > +	return (0);
> > +}
> > +
> > +/*
> > + * svm_handle_gexit
> > + *
> > + * Handle exits initiated by the guest due to #VC exceptions generated
> > + * when SEV-ES is enabled.
> > + */
> > +int
> > +svm_handle_gexit(struct vcpu *vcpu)
> > +{
> > +	struct vmcb		*vmcb = (struct vmcb *)vcpu->vc_control_va;
> > +	struct vm		*vm = vcpu->vc_parent;
> > +	struct ghcb_sa		*ghcb;
> > +	paddr_t			 ghcb_gpa, ghcb_hpa;
> > +	int			 syncout, error = 0;
> > +
> > +	if (vcpu->vc_svm_ghcb_va == 0) {
> > +		ghcb_gpa = vmcb->v_ghcb_gpa & PG_FRAME;
> > +		if (!pmap_extract(vm->vm_map->pmap, ghcb_gpa, &ghcb_hpa))
> > +			return (EINVAL);
> > +		vcpu->vc_svm_ghcb_va = (vaddr_t)PMAP_DIRECT_MAP(ghcb_hpa);
> > +	}
> > +
> > +	/* Verify GHCB and synchronize guest state information. */
> > +	ghcb = (struct ghcb_sa *)vcpu->vc_svm_ghcb_va;
> > +	if (svm_gexit_sync_host(vcpu)) {
> > +		error = EINVAL;
> > +		goto out;
> > +	}
> > +
> > +	/* Handle GHCB protocol */
> > +	syncout = 0;
> > +	switch (vmcb->v_exitcode) {
> > +	default:
> > +		DPRINTF("%s: unknown exit 0x%llx\n", __func__,
> > +		    vmcb->v_exitcode);
> > +		error = EINVAL;
> > +	}
> > +
> > +	if (syncout)
> > +		error = svm_gexit_sync_guest(vcpu);
> > +
> > +out:
> > +	return (error);
> > +}
> > +
> >  /*
> >   * svm_handle_efercr
> >   *
> 
> 

-------------------------------------------------------------------------
commit f7c73699b1e815e09746bceb3c9c691c6f168f28
Author: Hans-Joerg Hoexer <hshoexer@genua.de>
Date:   Tue Aug 13 11:39:36 2024 +0200

    vmm(4): Prepare handler for VMGEXIT
    
    SEV-ES enabled guests will convert non-automatic VM exits to VMGEXIT.
    The information needed, to handle the exit reason are provided by
    the guest in the GHCB.
    
    For example, when the guest needs to emulate the CPUID instruction,
    it will proivded the function value in %eax and the actual exit
    reason (SVM_VMEXIT_CPUID) in the GHCB.  And then the guest will
    switch back to vmm(4) using the VMGEXIT call.
    
    In vmm(4) svm_handle_gexit() will then sync the values provided in
    the GHCB with state information in struct vcpu and struct vmcb.
    Then it will emulate CPUID as usual.  After sychronizing the results
    back to the GHCB the guest can be resumed.
    
    For now provide a stub handler for SVM_VMEXIT_VMGEXIT and functions
    for synchronizing GHCB with struct vcpu and struct vmcb.  The
    switch-case statements in the stubs will error out.

diff --git a/sys/arch/amd64/amd64/vmm_machdep.c b/sys/arch/amd64/amd64/vmm_machdep.c
index ac6b3858d6c..1f59f150181 100644
--- a/sys/arch/amd64/amd64/vmm_machdep.c
+++ b/sys/arch/amd64/amd64/vmm_machdep.c
@@ -38,6 +38,7 @@
 #include <machine/segments.h>
 #include <machine/cpu.h>
 #include <machine/cpufunc.h>
+#include <machine/ghcb.h>
 #include <machine/vmmvar.h>
 
 #include <dev/isa/isareg.h>
@@ -97,6 +98,9 @@ int vmx_get_exit_info(uint64_t *, uint64_t *);
 int vmx_load_pdptes(struct vcpu *);
 int vmx_handle_exit(struct vcpu *);
 int svm_handle_exit(struct vcpu *);
+int svm_vmgexit_sync_host(struct vcpu *);
+int svm_vmgexit_sync_guest(struct vcpu *);
+int svm_handle_vmgexit(struct vcpu *);
 int svm_handle_efercr(struct vcpu *, uint64_t);
 int svm_get_iflag(struct vcpu *, uint64_t);
 int svm_handle_msr(struct vcpu *);
@@ -4282,6 +4286,9 @@ svm_handle_exit(struct vcpu *vcpu)
 		ret = svm_handle_efercr(vcpu, exit_reason);
 		update_rip = 0;
 		break;
+	case SVM_VMEXIT_VMGEXIT:
+		ret = svm_handle_vmgexit(vcpu);
+		break;
 	default:
 		DPRINTF("%s: unhandled exit 0x%llx (pa=0x%llx)\n", __func__,
 		    exit_reason, (uint64_t)vcpu->vc_control_pa);
@@ -4307,6 +4314,158 @@ svm_handle_exit(struct vcpu *vcpu)
 	return (ret);
 }
 
+/*
+ * sync guest ghcb -> host vmcb/vcpu
+ */
+int
+svm_vmgexit_sync_host(struct vcpu *vcpu)
+{
+	struct vmcb		*vmcb = (struct vmcb *)vcpu->vc_control_va;
+	struct ghcb_sa		*ghcb;
+	uint64_t		 svm_sw_exitcode;
+	uint8_t			*valid_bm, expected_bm[0x10];
+
+	if (!vcpu->vc_seves)
+		return (0);
+
+	if (vcpu->vc_svm_ghcb_va == 0)
+		return (0);
+	ghcb = (struct ghcb_sa *)vcpu->vc_svm_ghcb_va;
+
+	if (!ghcb_valid(ghcb))
+		return (EINVAL);
+	valid_bm = ghcb->valid_bitmap;
+
+	/* Always required. */
+	memset(expected_bm, 0, sizeof(expected_bm));
+	ghcb_valbm_set(expected_bm, GHCB_SW_EXITCODE);
+	ghcb_valbm_set(expected_bm, GHCB_SW_EXITINFO1);
+	ghcb_valbm_set(expected_bm, GHCB_SW_EXITINFO2);
+
+	svm_sw_exitcode = ghcb->v_sw_exitcode;
+	switch (svm_sw_exitcode) {
+	default:
+		return (EINVAL);
+	}
+
+	if (ghcb_verify_bm(valid_bm, expected_bm) != 0)
+		return (EINVAL);
+
+	/* Always required */
+	vmcb->v_exitcode = vcpu->vc_gueststate.vg_exit_reason =
+	    ghcb->v_sw_exitcode;
+	vmcb->v_exitinfo1 = ghcb->v_sw_exitinfo1;
+	vmcb->v_exitinfo2 = ghcb->v_sw_exitinfo2;
+
+	if (ghcb_valbm_isset(expected_bm, GHCB_RAX))
+		vmcb->v_rax = vcpu->vc_gueststate.vg_rax = ghcb->v_rax;
+	if (ghcb_valbm_isset(expected_bm, GHCB_RBX))
+		vcpu->vc_gueststate.vg_rbx = ghcb->v_rbx;
+	if (ghcb_valbm_isset(expected_bm, GHCB_RCX))
+		vcpu->vc_gueststate.vg_rcx = ghcb->v_rcx;
+	if (ghcb_valbm_isset(expected_bm, GHCB_RDX))
+		vcpu->vc_gueststate.vg_rdx = ghcb->v_rdx;
+
+	return (0);
+}
+
+/*
+ * sync host vmcb/vcpu -> guest ghcb
+ */
+int
+svm_vmgexit_sync_guest(struct vcpu *vcpu)
+{
+	uint64_t		 svm_sw_exitcode;
+	uint64_t		 svm_sw_exitinfo1, svm_sw_exitinfo2;
+	uint8_t			*valid_bm;
+	struct ghcb_sa		*ghcb;
+
+	if (!vcpu->vc_seves)
+		return (0);
+
+	if (vcpu->vc_svm_ghcb_va == 0)
+		return (0);
+
+	ghcb = (struct ghcb_sa *)vcpu->vc_svm_ghcb_va;
+	svm_sw_exitcode = ghcb->v_sw_exitcode;
+	svm_sw_exitinfo1 = ghcb->v_sw_exitinfo1;
+	svm_sw_exitinfo2 = ghcb->v_sw_exitinfo2;
+	ghcb_clear(ghcb);
+	valid_bm = ghcb->valid_bitmap;
+
+	switch (svm_sw_exitcode) {
+	default:
+		return (EINVAL);
+	}
+
+	/* Always required */
+	svm_sw_exitinfo1 = 0;
+	svm_sw_exitinfo2 = 0;
+	ghcb_valbm_set(valid_bm, GHCB_SW_EXITINFO1);
+	ghcb_valbm_set(valid_bm, GHCB_SW_EXITINFO2);
+
+	if (ghcb_valbm_isset(valid_bm, GHCB_RAX))
+		ghcb->v_rax = vcpu->vc_gueststate.vg_rax;
+	if (ghcb_valbm_isset(valid_bm, GHCB_RBX))
+		ghcb->v_rbx = vcpu->vc_gueststate.vg_rbx;
+	if (ghcb_valbm_isset(valid_bm, GHCB_RCX))
+		ghcb->v_rcx = vcpu->vc_gueststate.vg_rcx;
+	if (ghcb_valbm_isset(valid_bm, GHCB_RDX))
+		ghcb->v_rdx = vcpu->vc_gueststate.vg_rdx;
+
+	if (ghcb_valbm_isset(valid_bm, GHCB_SW_EXITINFO1))
+		ghcb->v_sw_exitinfo1 = svm_sw_exitinfo1;
+	if (ghcb_valbm_isset(valid_bm, GHCB_SW_EXITINFO2))
+		ghcb->v_sw_exitinfo2 = svm_sw_exitinfo2;
+
+	return (0);
+}
+
+/*
+ * svm_handle_vmgexit
+ *
+ * Handle exits initiated by the guest due to #VC exceptions generated
+ * when SEV-ES is enabled.
+ */
+int
+svm_handle_vmgexit(struct vcpu *vcpu)
+{
+	struct vmcb		*vmcb = (struct vmcb *)vcpu->vc_control_va;
+	struct vm		*vm = vcpu->vc_parent;
+	struct ghcb_sa		*ghcb;
+	paddr_t			 ghcb_gpa, ghcb_hpa;
+	int			 syncout, error = 0;
+
+	if (vcpu->vc_svm_ghcb_va == 0) {
+		ghcb_gpa = vmcb->v_ghcb_gpa & PG_FRAME;
+		if (!pmap_extract(vm->vm_pmap, ghcb_gpa, &ghcb_hpa))
+			return (EINVAL);
+		vcpu->vc_svm_ghcb_va = (vaddr_t)PMAP_DIRECT_MAP(ghcb_hpa);
+	}
+
+	/* Verify GHCB and synchronize guest state information. */
+	ghcb = (struct ghcb_sa *)vcpu->vc_svm_ghcb_va;
+	if (svm_vmgexit_sync_host(vcpu)) {
+		error = EINVAL;
+		goto out;
+	}
+
+	/* Handle GHCB protocol */
+	syncout = 0;
+	switch (vmcb->v_exitcode) {
+	default:
+		DPRINTF("%s: unknown exit 0x%llx\n", __func__,
+		    vmcb->v_exitcode);
+		error = EINVAL;
+	}
+
+	if (syncout)
+		error = svm_vmgexit_sync_guest(vcpu);
+
+out:
+	return (error);
+}
+
 /*
  * svm_handle_efercr
  *