@@ -594,6 +594,73 @@ static enum es_result vc_handle_mmio_twobyte_ops(struct ghcb *ghcb,
594594 return ret ;
595595}
596596
597+ /*
598+ * The MOVS instruction has two memory operands, which raises the
599+ * problem that it is not known whether the access to the source or the
600+ * destination caused the #VC exception (and hence whether an MMIO read
601+ * or write operation needs to be emulated).
602+ *
603+ * Instead of playing games with walking page-tables and trying to guess
604+ * whether the source or destination is an MMIO range, split the move
605+ * into two operations, a read and a write with only one memory operand.
606+ * This will cause a nested #VC exception on the MMIO address which can
607+ * then be handled.
608+ *
609+ * This implementation has the benefit that it also supports MOVS where
610+ * source _and_ destination are MMIO regions.
611+ *
612+ * It will slow MOVS on MMIO down a lot, but in SEV-ES guests it is a
613+ * rare operation. If it turns out to be a performance problem the split
614+ * operations can be moved to memcpy_fromio() and memcpy_toio().
615+ */
616+ static enum es_result vc_handle_mmio_movs (struct es_em_ctxt * ctxt ,
617+ unsigned int bytes )
618+ {
619+ unsigned long ds_base , es_base ;
620+ unsigned char * src , * dst ;
621+ unsigned char buffer [8 ];
622+ enum es_result ret ;
623+ bool rep ;
624+ int off ;
625+
626+ ds_base = insn_get_seg_base (ctxt -> regs , INAT_SEG_REG_DS );
627+ es_base = insn_get_seg_base (ctxt -> regs , INAT_SEG_REG_ES );
628+
629+ if (ds_base == -1L || es_base == -1L ) {
630+ ctxt -> fi .vector = X86_TRAP_GP ;
631+ ctxt -> fi .error_code = 0 ;
632+ return ES_EXCEPTION ;
633+ }
634+
635+ src = ds_base + (unsigned char * )ctxt -> regs -> si ;
636+ dst = es_base + (unsigned char * )ctxt -> regs -> di ;
637+
638+ ret = vc_read_mem (ctxt , src , buffer , bytes );
639+ if (ret != ES_OK )
640+ return ret ;
641+
642+ ret = vc_write_mem (ctxt , dst , buffer , bytes );
643+ if (ret != ES_OK )
644+ return ret ;
645+
646+ if (ctxt -> regs -> flags & X86_EFLAGS_DF )
647+ off = - bytes ;
648+ else
649+ off = bytes ;
650+
651+ ctxt -> regs -> si += off ;
652+ ctxt -> regs -> di += off ;
653+
654+ rep = insn_has_rep_prefix (& ctxt -> insn );
655+ if (rep )
656+ ctxt -> regs -> cx -= 1 ;
657+
658+ if (!rep || ctxt -> regs -> cx == 0 )
659+ return ES_OK ;
660+ else
661+ return ES_RETRY ;
662+ }
663+
597664static enum es_result vc_handle_mmio (struct ghcb * ghcb ,
598665 struct es_em_ctxt * ctxt )
599666{
@@ -655,6 +722,16 @@ static enum es_result vc_handle_mmio(struct ghcb *ghcb,
655722 memcpy (reg_data , ghcb -> shared_buffer , bytes );
656723 break ;
657724
725+ /* MOVS instruction */
726+ case 0xa4 :
727+ bytes = 1 ;
728+ fallthrough ;
729+ case 0xa5 :
730+ if (!bytes )
731+ bytes = insn -> opnd_bytes ;
732+
733+ ret = vc_handle_mmio_movs (ctxt , bytes );
734+ break ;
658735 /* Two-Byte Opcodes */
659736 case 0x0f :
660737 ret = vc_handle_mmio_twobyte_ops (ghcb , ctxt );
0 commit comments