diff mbox series

[kvm-unit-tests,v2,10/10] x86: AMD SEV-ES: Handle string IO for IOIO #VC

Message ID 20220209164420.8894-11-varad.gautam@suse.com (mailing list archive)
State New, archived
Headers show
Series Add #VC exception handling for AMD SEV-ES | expand

Commit Message

Varad Gautam Feb. 9, 2022, 4:44 p.m. UTC
Using Linux's IOIO #VC processing logic.

Signed-off-by: Varad Gautam <varad.gautam@suse.com>
---
 lib/x86/amd_sev_vc.c | 108 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 106 insertions(+), 2 deletions(-)

Comments

Marc Orr Feb. 13, 2022, 1:31 a.m. UTC | #1
On Wed, Feb 9, 2022 at 8:44 AM Varad Gautam <varad.gautam@suse.com> wrote:
>
> Using Linux's IOIO #VC processing logic.
>
> Signed-off-by: Varad Gautam <varad.gautam@suse.com>
> ---
>  lib/x86/amd_sev_vc.c | 108 ++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 106 insertions(+), 2 deletions(-)
>
> diff --git a/lib/x86/amd_sev_vc.c b/lib/x86/amd_sev_vc.c
> index 88c95e1..c79d9be 100644
> --- a/lib/x86/amd_sev_vc.c
> +++ b/lib/x86/amd_sev_vc.c
> @@ -278,10 +278,46 @@ static enum es_result vc_ioio_exitinfo(struct es_em_ctxt *ctxt, u64 *exitinfo)
>         return ES_OK;
>  }
>
> +static enum es_result vc_insn_string_read(struct es_em_ctxt *ctxt,
> +                                         void *src, unsigned char *buf,
> +                                         unsigned int data_size,
> +                                         unsigned int count,
> +                                         bool backwards)
> +{
> +       int i, b = backwards ? -1 : 1;
> +
> +       for (i = 0; i < count; i++) {
> +               void *s = src + (i * data_size * b);
> +               unsigned char *d = buf + (i * data_size);
> +
> +               memcpy(d, s, data_size);
> +       }
> +
> +       return ES_OK;
> +}
> +
> +static enum es_result vc_insn_string_write(struct es_em_ctxt *ctxt,
> +                                          void *dst, unsigned char *buf,
> +                                          unsigned int data_size,
> +                                          unsigned int count,
> +                                          bool backwards)
> +{
> +       int i, s = backwards ? -1 : 1;
> +
> +       for (i = 0; i < count; i++) {
> +               void *d = dst + (i * data_size * s);
> +               unsigned char *b = buf + (i * data_size);
> +
> +               memcpy(d, b, data_size);
> +       }
> +
> +       return ES_OK;
> +}
> +
>  static enum es_result vc_handle_ioio(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
>  {
>         struct ex_regs *regs = ctxt->regs;
> -       u64 exit_info_1;
> +       u64 exit_info_1, exit_info_2;
>         enum es_result ret;
>
>         ret = vc_ioio_exitinfo(ctxt, &exit_info_1);
> @@ -289,7 +325,75 @@ static enum es_result vc_handle_ioio(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
>                 return ret;
>
>         if (exit_info_1 & IOIO_TYPE_STR) {
> -               ret = ES_VMM_ERROR;
> +               /* (REP) INS/OUTS */
> +
> +               bool df = ((regs->rflags & X86_EFLAGS_DF) == X86_EFLAGS_DF);
> +               unsigned int io_bytes, exit_bytes;
> +               unsigned int ghcb_count, op_count;
> +               unsigned long es_base;
> +               u64 sw_scratch;
> +
> +               /*
> +                * For the string variants with rep prefix the amount of in/out
> +                * operations per #VC exception is limited so that the kernel
> +                * has a chance to take interrupts and re-schedule while the
> +                * instruction is emulated.
> +                */
> +               io_bytes   = (exit_info_1 >> 4) & 0x7;
> +               ghcb_count = sizeof(ghcb->shared_buffer) / io_bytes;
> +
> +               op_count    = (exit_info_1 & IOIO_REP) ? regs->rcx : 1;
> +               exit_info_2 = op_count < ghcb_count ? op_count : ghcb_count;
> +               exit_bytes  = exit_info_2 * io_bytes;
> +
> +               es_base = 0;
> +
> +               /* Read bytes of OUTS into the shared buffer */
> +               if (!(exit_info_1 & IOIO_TYPE_IN)) {
> +                       ret = vc_insn_string_read(ctxt,
> +                                              (void *)(es_base + regs->rsi),
> +                                              ghcb->shared_buffer, io_bytes,
> +                                              exit_info_2, df);
> +                       if (ret)
> +                               return ret;
> +               }
> +
> +               /*
> +                * Issue an VMGEXIT to the HV to consume the bytes from the
> +                * shared buffer or to have it write them into the shared buffer
> +                * depending on the instruction: OUTS or INS.
> +                */
> +               sw_scratch = __pa(ghcb) + offsetof(struct ghcb, shared_buffer);
> +               ghcb_set_sw_scratch(ghcb, sw_scratch);
> +               ret = sev_es_ghcb_hv_call(ghcb, ctxt, SVM_EXIT_IOIO,
> +                                         exit_info_1, exit_info_2);
> +               if (ret != ES_OK)
> +                       return ret;
> +
> +               /* Read bytes from shared buffer into the guest's destination. */
> +               if (exit_info_1 & IOIO_TYPE_IN) {
> +                       ret = vc_insn_string_write(ctxt,
> +                                                  (void *)(es_base + regs->rdi),
> +                                                  ghcb->shared_buffer, io_bytes,
> +                                                  exit_info_2, df);
> +                       if (ret)
> +                               return ret;
> +
> +                       if (df)
> +                               regs->rdi -= exit_bytes;
> +                       else
> +                               regs->rdi += exit_bytes;
> +               } else {
> +                       if (df)
> +                               regs->rsi -= exit_bytes;
> +                       else
> +                               regs->rsi += exit_bytes;
> +               }
> +
> +               if (exit_info_1 & IOIO_REP)
> +                       regs->rcx -= exit_info_2;
> +
> +               ret = regs->rcx ? ES_RETRY : ES_OK;
>         } else {
>                 /* IN/OUT into/from rAX */
>
> --
> 2.32.0
>

I was able to run both the amd_sev and msr tests under SEV-ES using
this built-in #VC handler on my setup. Obviously, that doesn't
exercise all of this #VC handler code. But I also compared it against
what's in LInux and read through all of it as well. Great job, Varad!

Reviewed-by: Marc Orr <marcorr@google.com>
Tested-by: Marc Orr <marcorr@google.com>
Varad Gautam Feb. 24, 2022, 9:42 a.m. UTC | #2
On 2/13/22 2:31 AM, Marc Orr wrote:
> On Wed, Feb 9, 2022 at 8:44 AM Varad Gautam <varad.gautam@suse.com> wrote:
>>
>> Using Linux's IOIO #VC processing logic.
>>
>> Signed-off-by: Varad Gautam <varad.gautam@suse.com>
>> ---
>>  lib/x86/amd_sev_vc.c | 108 ++++++++++++++++++++++++++++++++++++++++++-
>>  1 file changed, 106 insertions(+), 2 deletions(-)
>>
>> diff --git a/lib/x86/amd_sev_vc.c b/lib/x86/amd_sev_vc.c
>> index 88c95e1..c79d9be 100644
>> --- a/lib/x86/amd_sev_vc.c
>> +++ b/lib/x86/amd_sev_vc.c
>> @@ -278,10 +278,46 @@ static enum es_result vc_ioio_exitinfo(struct es_em_ctxt *ctxt, u64 *exitinfo)
>>         return ES_OK;
>>  }
>>
>> +static enum es_result vc_insn_string_read(struct es_em_ctxt *ctxt,
>> +                                         void *src, unsigned char *buf,
>> +                                         unsigned int data_size,
>> +                                         unsigned int count,
>> +                                         bool backwards)
>> +{
>> +       int i, b = backwards ? -1 : 1;
>> +
>> +       for (i = 0; i < count; i++) {
>> +               void *s = src + (i * data_size * b);
>> +               unsigned char *d = buf + (i * data_size);
>> +
>> +               memcpy(d, s, data_size);
>> +       }
>> +
>> +       return ES_OK;
>> +}
>> +
>> +static enum es_result vc_insn_string_write(struct es_em_ctxt *ctxt,
>> +                                          void *dst, unsigned char *buf,
>> +                                          unsigned int data_size,
>> +                                          unsigned int count,
>> +                                          bool backwards)
>> +{
>> +       int i, s = backwards ? -1 : 1;
>> +
>> +       for (i = 0; i < count; i++) {
>> +               void *d = dst + (i * data_size * s);
>> +               unsigned char *b = buf + (i * data_size);
>> +
>> +               memcpy(d, b, data_size);
>> +       }
>> +
>> +       return ES_OK;
>> +}
>> +
>>  static enum es_result vc_handle_ioio(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
>>  {
>>         struct ex_regs *regs = ctxt->regs;
>> -       u64 exit_info_1;
>> +       u64 exit_info_1, exit_info_2;
>>         enum es_result ret;
>>
>>         ret = vc_ioio_exitinfo(ctxt, &exit_info_1);
>> @@ -289,7 +325,75 @@ static enum es_result vc_handle_ioio(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
>>                 return ret;
>>
>>         if (exit_info_1 & IOIO_TYPE_STR) {
>> -               ret = ES_VMM_ERROR;
>> +               /* (REP) INS/OUTS */
>> +
>> +               bool df = ((regs->rflags & X86_EFLAGS_DF) == X86_EFLAGS_DF);
>> +               unsigned int io_bytes, exit_bytes;
>> +               unsigned int ghcb_count, op_count;
>> +               unsigned long es_base;
>> +               u64 sw_scratch;
>> +
>> +               /*
>> +                * For the string variants with rep prefix the amount of in/out
>> +                * operations per #VC exception is limited so that the kernel
>> +                * has a chance to take interrupts and re-schedule while the
>> +                * instruction is emulated.
>> +                */
>> +               io_bytes   = (exit_info_1 >> 4) & 0x7;
>> +               ghcb_count = sizeof(ghcb->shared_buffer) / io_bytes;
>> +
>> +               op_count    = (exit_info_1 & IOIO_REP) ? regs->rcx : 1;
>> +               exit_info_2 = op_count < ghcb_count ? op_count : ghcb_count;
>> +               exit_bytes  = exit_info_2 * io_bytes;
>> +
>> +               es_base = 0;
>> +
>> +               /* Read bytes of OUTS into the shared buffer */
>> +               if (!(exit_info_1 & IOIO_TYPE_IN)) {
>> +                       ret = vc_insn_string_read(ctxt,
>> +                                              (void *)(es_base + regs->rsi),
>> +                                              ghcb->shared_buffer, io_bytes,
>> +                                              exit_info_2, df);
>> +                       if (ret)
>> +                               return ret;
>> +               }
>> +
>> +               /*
>> +                * Issue an VMGEXIT to the HV to consume the bytes from the
>> +                * shared buffer or to have it write them into the shared buffer
>> +                * depending on the instruction: OUTS or INS.
>> +                */
>> +               sw_scratch = __pa(ghcb) + offsetof(struct ghcb, shared_buffer);
>> +               ghcb_set_sw_scratch(ghcb, sw_scratch);
>> +               ret = sev_es_ghcb_hv_call(ghcb, ctxt, SVM_EXIT_IOIO,
>> +                                         exit_info_1, exit_info_2);
>> +               if (ret != ES_OK)
>> +                       return ret;
>> +
>> +               /* Read bytes from shared buffer into the guest's destination. */
>> +               if (exit_info_1 & IOIO_TYPE_IN) {
>> +                       ret = vc_insn_string_write(ctxt,
>> +                                                  (void *)(es_base + regs->rdi),
>> +                                                  ghcb->shared_buffer, io_bytes,
>> +                                                  exit_info_2, df);
>> +                       if (ret)
>> +                               return ret;
>> +
>> +                       if (df)
>> +                               regs->rdi -= exit_bytes;
>> +                       else
>> +                               regs->rdi += exit_bytes;
>> +               } else {
>> +                       if (df)
>> +                               regs->rsi -= exit_bytes;
>> +                       else
>> +                               regs->rsi += exit_bytes;
>> +               }
>> +
>> +               if (exit_info_1 & IOIO_REP)
>> +                       regs->rcx -= exit_info_2;
>> +
>> +               ret = regs->rcx ? ES_RETRY : ES_OK;
>>         } else {
>>                 /* IN/OUT into/from rAX */
>>
>> --
>> 2.32.0
>>
> 
> I was able to run both the amd_sev and msr tests under SEV-ES using
> this built-in #VC handler on my setup. Obviously, that doesn't
> exercise all of this #VC handler code. But I also compared it against
> what's in LInux and read through all of it as well. Great job, Varad!
> 
> Reviewed-by: Marc Orr <marcorr@google.com>
> Tested-by: Marc Orr <marcorr@google.com>
> 

Thank you Marc for going over the series! I'll have the v3 out soon.

Regards,
Varad
diff mbox series

Patch

diff --git a/lib/x86/amd_sev_vc.c b/lib/x86/amd_sev_vc.c
index 88c95e1..c79d9be 100644
--- a/lib/x86/amd_sev_vc.c
+++ b/lib/x86/amd_sev_vc.c
@@ -278,10 +278,46 @@  static enum es_result vc_ioio_exitinfo(struct es_em_ctxt *ctxt, u64 *exitinfo)
 	return ES_OK;
 }
 
+static enum es_result vc_insn_string_read(struct es_em_ctxt *ctxt,
+					  void *src, unsigned char *buf,
+					  unsigned int data_size,
+					  unsigned int count,
+					  bool backwards)
+{
+	int i, b = backwards ? -1 : 1;
+
+	for (i = 0; i < count; i++) {
+		void *s = src + (i * data_size * b);
+		unsigned char *d = buf + (i * data_size);
+
+		memcpy(d, s, data_size);
+	}
+
+	return ES_OK;
+}
+
+static enum es_result vc_insn_string_write(struct es_em_ctxt *ctxt,
+					   void *dst, unsigned char *buf,
+					   unsigned int data_size,
+					   unsigned int count,
+					   bool backwards)
+{
+	int i, s = backwards ? -1 : 1;
+
+	for (i = 0; i < count; i++) {
+		void *d = dst + (i * data_size * s);
+		unsigned char *b = buf + (i * data_size);
+
+		memcpy(d, b, data_size);
+	}
+
+	return ES_OK;
+}
+
 static enum es_result vc_handle_ioio(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
 {
 	struct ex_regs *regs = ctxt->regs;
-	u64 exit_info_1;
+	u64 exit_info_1, exit_info_2;
 	enum es_result ret;
 
 	ret = vc_ioio_exitinfo(ctxt, &exit_info_1);
@@ -289,7 +325,75 @@  static enum es_result vc_handle_ioio(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
 		return ret;
 
 	if (exit_info_1 & IOIO_TYPE_STR) {
-		ret = ES_VMM_ERROR;
+		/* (REP) INS/OUTS */
+
+		bool df = ((regs->rflags & X86_EFLAGS_DF) == X86_EFLAGS_DF);
+		unsigned int io_bytes, exit_bytes;
+		unsigned int ghcb_count, op_count;
+		unsigned long es_base;
+		u64 sw_scratch;
+
+		/*
+		 * For the string variants with rep prefix the amount of in/out
+		 * operations per #VC exception is limited so that the kernel
+		 * has a chance to take interrupts and re-schedule while the
+		 * instruction is emulated.
+		 */
+		io_bytes   = (exit_info_1 >> 4) & 0x7;
+		ghcb_count = sizeof(ghcb->shared_buffer) / io_bytes;
+
+		op_count    = (exit_info_1 & IOIO_REP) ? regs->rcx : 1;
+		exit_info_2 = op_count < ghcb_count ? op_count : ghcb_count;
+		exit_bytes  = exit_info_2 * io_bytes;
+
+		es_base = 0;
+
+		/* Read bytes of OUTS into the shared buffer */
+		if (!(exit_info_1 & IOIO_TYPE_IN)) {
+			ret = vc_insn_string_read(ctxt,
+					       (void *)(es_base + regs->rsi),
+					       ghcb->shared_buffer, io_bytes,
+					       exit_info_2, df);
+			if (ret)
+				return ret;
+		}
+
+		/*
+		 * Issue an VMGEXIT to the HV to consume the bytes from the
+		 * shared buffer or to have it write them into the shared buffer
+		 * depending on the instruction: OUTS or INS.
+		 */
+		sw_scratch = __pa(ghcb) + offsetof(struct ghcb, shared_buffer);
+		ghcb_set_sw_scratch(ghcb, sw_scratch);
+		ret = sev_es_ghcb_hv_call(ghcb, ctxt, SVM_EXIT_IOIO,
+					  exit_info_1, exit_info_2);
+		if (ret != ES_OK)
+			return ret;
+
+		/* Read bytes from shared buffer into the guest's destination. */
+		if (exit_info_1 & IOIO_TYPE_IN) {
+			ret = vc_insn_string_write(ctxt,
+						   (void *)(es_base + regs->rdi),
+						   ghcb->shared_buffer, io_bytes,
+						   exit_info_2, df);
+			if (ret)
+				return ret;
+
+			if (df)
+				regs->rdi -= exit_bytes;
+			else
+				regs->rdi += exit_bytes;
+		} else {
+			if (df)
+				regs->rsi -= exit_bytes;
+			else
+				regs->rsi += exit_bytes;
+		}
+
+		if (exit_info_1 & IOIO_REP)
+			regs->rcx -= exit_info_2;
+
+		ret = regs->rcx ? ES_RETRY : ES_OK;
 	} else {
 		/* IN/OUT into/from rAX */