[edk2-devel] [PATCH v14 17/46] OvmfPkg/VmgExitLib: Add support for NPF NAE events (MMIO)

Lendacky, Thomas thomas.lendacky at amd.com
Fri Aug 7 19:39:02 UTC 2020


From: Tom Lendacky <thomas.lendacky at amd.com>

BZ: https://bugzilla.tianocore.org/show_bug.cgi?id=2198

Under SEV-ES, a NPF intercept for an NPT entry with a reserved bit set
generates a #VC exception. This condition is assumed to be an MMIO access.
VMGEXIT must be used to allow the hypervisor to handle this intercept.

Add support to construct the required GHCB values to support a NPF NAE
event for MMIO.  Parse the instruction that generated the #VC exception,
setting the required register values in the GHCB and creating the proper
SW_EXIT_INFO1, SW_EXITINFO2 and SW_SCRATCH values in the GHCB.

Cc: Jordan Justen <jordan.l.justen at intel.com>
Cc: Laszlo Ersek <lersek at redhat.com>
Cc: Ard Biesheuvel <ard.biesheuvel at arm.com>
Acked-by: Laszlo Ersek <lersek at redhat.com>
Signed-off-by: Tom Lendacky <thomas.lendacky at amd.com>
---
 OvmfPkg/Library/VmgExitLib/VmgExitVcHandler.c | 486 ++++++++++++++++++
 1 file changed, 486 insertions(+)

diff --git a/OvmfPkg/Library/VmgExitLib/VmgExitVcHandler.c b/OvmfPkg/Library/VmgExitLib/VmgExitVcHandler.c
index dbedd4e9f95d..0e502ac14819 100644
--- a/OvmfPkg/Library/VmgExitLib/VmgExitVcHandler.c
+++ b/OvmfPkg/Library/VmgExitLib/VmgExitVcHandler.c
@@ -183,6 +183,281 @@ GhcbSetRegValid (
   Ghcb->SaveArea.ValidBitmap[RegIndex] |= (1 << RegBit);
 }
 
+/**
+  Return a pointer to the contents of the specified register.
+
+  Based upon the input register, return a pointer to the registers contents
+  in the x86 processor context.
+
+  @param[in] Regs      x64 processor context
+  @param[in] Register  Register to obtain pointer for
+
+  @return              Pointer to the contents of the requested register
+
+**/
+STATIC
+UINT64 *
+GetRegisterPointer (
+  IN EFI_SYSTEM_CONTEXT_X64   *Regs,
+  IN UINT8                    Register
+  )
+{
+  UINT64 *Reg;
+
+  switch (Register) {
+  case 0:
+    Reg = &Regs->Rax;
+    break;
+  case 1:
+    Reg = &Regs->Rcx;
+    break;
+  case 2:
+    Reg = &Regs->Rdx;
+    break;
+  case 3:
+    Reg = &Regs->Rbx;
+    break;
+  case 4:
+    Reg = &Regs->Rsp;
+    break;
+  case 5:
+    Reg = &Regs->Rbp;
+    break;
+  case 6:
+    Reg = &Regs->Rsi;
+    break;
+  case 7:
+    Reg = &Regs->Rdi;
+    break;
+  case 8:
+    Reg = &Regs->R8;
+    break;
+  case 9:
+    Reg = &Regs->R9;
+    break;
+  case 10:
+    Reg = &Regs->R10;
+    break;
+  case 11:
+    Reg = &Regs->R11;
+    break;
+  case 12:
+    Reg = &Regs->R12;
+    break;
+  case 13:
+    Reg = &Regs->R13;
+    break;
+  case 14:
+    Reg = &Regs->R14;
+    break;
+  case 15:
+    Reg = &Regs->R15;
+    break;
+  default:
+    Reg = NULL;
+  }
+  ASSERT (Reg != NULL);
+
+  return Reg;
+}
+
+/**
+  Update the instruction parsing context for displacement bytes.
+
+  @param[in, out] InstructionData  Instruction parsing context
+  @param[in]      Size             The instruction displacement size
+
+**/
+STATIC
+VOID
+UpdateForDisplacement (
+  IN OUT SEV_ES_INSTRUCTION_DATA  *InstructionData,
+  IN     UINTN                    Size
+  )
+{
+  InstructionData->DisplacementSize = Size;
+  InstructionData->Immediate += Size;
+  InstructionData->End += Size;
+}
+
+/**
+  Determine if an instruction address if RIP relative.
+
+  Examine the instruction parsing context to determine if the address offset
+  is relative to the instruction pointer.
+
+  @param[in] InstructionData  Instruction parsing context
+
+  @retval TRUE                Instruction addressing is RIP relative
+  @retval FALSE               Instruction addressing is not RIP relative
+
+**/
+STATIC
+BOOLEAN
+IsRipRelative (
+  IN SEV_ES_INSTRUCTION_DATA  *InstructionData
+  )
+{
+  SEV_ES_INSTRUCTION_OPCODE_EXT  *Ext;
+
+  Ext = &InstructionData->Ext;
+
+  return ((InstructionData->Mode == LongMode64Bit) &&
+          (Ext->ModRm.Mod == 0) &&
+          (Ext->ModRm.Rm == 5)  &&
+          (InstructionData->SibPresent == FALSE));
+}
+
+/**
+  Return the effective address of a memory operand.
+
+  Examine the instruction parsing context to obtain the effective memory
+  address of a memory operand.
+
+  @param[in] Regs             x64 processor context
+  @param[in] InstructionData  Instruction parsing context
+
+  @return                     The memory operand effective address
+
+**/
+STATIC
+UINT64
+GetEffectiveMemoryAddress (
+  IN EFI_SYSTEM_CONTEXT_X64   *Regs,
+  IN SEV_ES_INSTRUCTION_DATA  *InstructionData
+  )
+{
+  SEV_ES_INSTRUCTION_OPCODE_EXT  *Ext;
+  UINT64                         EffectiveAddress;
+
+  Ext = &InstructionData->Ext;
+  EffectiveAddress = 0;
+
+  if (IsRipRelative (InstructionData)) {
+    //
+    // RIP-relative displacement is a 32-bit signed value
+    //
+    INT32 RipRelative;
+
+    RipRelative = *(INT32 *) InstructionData->Displacement;
+
+    UpdateForDisplacement (InstructionData, 4);
+
+    //
+    // Negative displacement is handled by standard UINT64 wrap-around.
+    //
+    return Regs->Rip + (UINT64) RipRelative;
+  }
+
+  switch (Ext->ModRm.Mod) {
+  case 1:
+    UpdateForDisplacement (InstructionData, 1);
+    EffectiveAddress += (UINT64) (*(INT8 *) (InstructionData->Displacement));
+    break;
+  case 2:
+    switch (InstructionData->AddrSize) {
+    case Size16Bits:
+      UpdateForDisplacement (InstructionData, 2);
+      EffectiveAddress += (UINT64) (*(INT16 *) (InstructionData->Displacement));
+      break;
+    default:
+      UpdateForDisplacement (InstructionData, 4);
+      EffectiveAddress += (UINT64) (*(INT32 *) (InstructionData->Displacement));
+      break;
+    }
+    break;
+  }
+
+  if (InstructionData->SibPresent) {
+    INT64  Displacement;
+
+    if (Ext->Sib.Index != 4) {
+      CopyMem (
+        &Displacement,
+        GetRegisterPointer (Regs, Ext->Sib.Index),
+        sizeof (Displacement)
+        );
+      Displacement *= (INT64)(1 << Ext->Sib.Scale);
+
+      //
+      // Negative displacement is handled by standard UINT64 wrap-around.
+      //
+      EffectiveAddress += (UINT64) Displacement;
+    }
+
+    if ((Ext->Sib.Base != 5) || Ext->ModRm.Mod) {
+      EffectiveAddress += *GetRegisterPointer (Regs, Ext->Sib.Base);
+    } else {
+      UpdateForDisplacement (InstructionData, 4);
+      EffectiveAddress += (UINT64) (*(INT32 *) (InstructionData->Displacement));
+    }
+  } else {
+    EffectiveAddress += *GetRegisterPointer (Regs, Ext->ModRm.Rm);
+  }
+
+  return EffectiveAddress;
+}
+
+/**
+  Decode a ModRM byte.
+
+  Examine the instruction parsing context to decode a ModRM byte and the SIB
+  byte, if present.
+
+  @param[in]      Regs             x64 processor context
+  @param[in, out] InstructionData  Instruction parsing context
+
+**/
+STATIC
+VOID
+DecodeModRm (
+  IN     EFI_SYSTEM_CONTEXT_X64   *Regs,
+  IN OUT SEV_ES_INSTRUCTION_DATA  *InstructionData
+  )
+{
+  SEV_ES_INSTRUCTION_OPCODE_EXT  *Ext;
+  INSTRUCTION_REX_PREFIX         *RexPrefix;
+  INSTRUCTION_MODRM              *ModRm;
+  INSTRUCTION_SIB                *Sib;
+
+  RexPrefix = &InstructionData->RexPrefix;
+  Ext = &InstructionData->Ext;
+  ModRm = &InstructionData->ModRm;
+  Sib = &InstructionData->Sib;
+
+  InstructionData->ModRmPresent = TRUE;
+  ModRm->Uint8 = *(InstructionData->End);
+
+  InstructionData->Displacement++;
+  InstructionData->Immediate++;
+  InstructionData->End++;
+
+  Ext->ModRm.Mod = ModRm->Bits.Mod;
+  Ext->ModRm.Reg = (RexPrefix->Bits.BitR << 3) | ModRm->Bits.Reg;
+  Ext->ModRm.Rm  = (RexPrefix->Bits.BitB << 3) | ModRm->Bits.Rm;
+
+  Ext->RegData = *GetRegisterPointer (Regs, Ext->ModRm.Reg);
+
+  if (Ext->ModRm.Mod == 3) {
+    Ext->RmData = *GetRegisterPointer (Regs, Ext->ModRm.Rm);
+  } else {
+    if (ModRm->Bits.Rm == 4) {
+      InstructionData->SibPresent = TRUE;
+      Sib->Uint8 = *(InstructionData->End);
+
+      InstructionData->Displacement++;
+      InstructionData->Immediate++;
+      InstructionData->End++;
+
+      Ext->Sib.Scale = Sib->Bits.Scale;
+      Ext->Sib.Index = (RexPrefix->Bits.BitX << 3) | Sib->Bits.Index;
+      Ext->Sib.Base  = (RexPrefix->Bits.BitB << 3) | Sib->Bits.Base;
+    }
+
+    Ext->RmData = GetEffectiveMemoryAddress (Regs, InstructionData);
+  }
+}
+
 /**
   Decode instruction prefixes.
 
@@ -374,6 +649,213 @@ UnsupportedExit (
   return Status;
 }
 
+/**
+  Handle an MMIO event.
+
+  Use the VMGEXIT instruction to handle either an MMIO read or an MMIO write.
+
+  @param[in, out] Ghcb             Pointer to the Guest-Hypervisor Communication
+                                   Block
+  @param[in, out] Regs             x64 processor context
+  @param[in, out] InstructionData  Instruction parsing context
+
+  @retval 0                        Event handled successfully
+  @return                          New exception value to propagate
+
+**/
+STATIC
+UINT64
+MmioExit (
+  IN OUT GHCB                     *Ghcb,
+  IN OUT EFI_SYSTEM_CONTEXT_X64   *Regs,
+  IN OUT SEV_ES_INSTRUCTION_DATA  *InstructionData
+  )
+{
+  UINT64  ExitInfo1, ExitInfo2, Status;
+  UINTN   Bytes;
+  UINT64  *Register;
+  UINT8   OpCode, SignByte;
+
+  Bytes = 0;
+
+  OpCode = *(InstructionData->OpCodes);
+  if (OpCode == TWO_BYTE_OPCODE_ESCAPE) {
+    OpCode = *(InstructionData->OpCodes + 1);
+  }
+
+  switch (OpCode) {
+  //
+  // MMIO write (MOV reg/memX, regX)
+  //
+  case 0x88:
+    Bytes = 1;
+    //
+    // fall through
+    //
+  case 0x89:
+    DecodeModRm (Regs, InstructionData);
+    Bytes = ((Bytes != 0) ? Bytes :
+             (InstructionData->DataSize == Size16Bits) ? 2 :
+             (InstructionData->DataSize == Size32Bits) ? 4 :
+             (InstructionData->DataSize == Size64Bits) ? 8 :
+             0);
+
+    if (InstructionData->Ext.ModRm.Mod == 3) {
+      //
+      // NPF on two register operands???
+      //
+      return UnsupportedExit (Ghcb, Regs, InstructionData);
+    }
+
+    ExitInfo1 = InstructionData->Ext.RmData;
+    ExitInfo2 = Bytes;
+    CopyMem (Ghcb->SharedBuffer, &InstructionData->Ext.RegData, Bytes);
+
+    Ghcb->SaveArea.SwScratch = (UINT64) Ghcb->SharedBuffer;
+    Status = VmgExit (Ghcb, SVM_EXIT_MMIO_WRITE, ExitInfo1, ExitInfo2);
+    if (Status != 0) {
+      return Status;
+    }
+    break;
+
+  //
+  // MMIO write (MOV reg/memX, immX)
+  //
+  case 0xC6:
+    Bytes = 1;
+    //
+    // fall through
+    //
+  case 0xC7:
+    DecodeModRm (Regs, InstructionData);
+    Bytes = ((Bytes != 0) ? Bytes :
+             (InstructionData->DataSize == Size16Bits) ? 2 :
+             (InstructionData->DataSize == Size32Bits) ? 4 :
+             0);
+
+    InstructionData->ImmediateSize = Bytes;
+    InstructionData->End += Bytes;
+
+    ExitInfo1 = InstructionData->Ext.RmData;
+    ExitInfo2 = Bytes;
+    CopyMem (Ghcb->SharedBuffer, InstructionData->Immediate, Bytes);
+
+    Ghcb->SaveArea.SwScratch = (UINT64) Ghcb->SharedBuffer;
+    Status = VmgExit (Ghcb, SVM_EXIT_MMIO_WRITE, ExitInfo1, ExitInfo2);
+    if (Status != 0) {
+      return Status;
+    }
+    break;
+
+  //
+  // MMIO read (MOV regX, reg/memX)
+  //
+  case 0x8A:
+    Bytes = 1;
+    //
+    // fall through
+    //
+  case 0x8B:
+    DecodeModRm (Regs, InstructionData);
+    Bytes = ((Bytes != 0) ? Bytes :
+             (InstructionData->DataSize == Size16Bits) ? 2 :
+             (InstructionData->DataSize == Size32Bits) ? 4 :
+             (InstructionData->DataSize == Size64Bits) ? 8 :
+             0);
+    if (InstructionData->Ext.ModRm.Mod == 3) {
+      //
+      // NPF on two register operands???
+      //
+      return UnsupportedExit (Ghcb, Regs, InstructionData);
+    }
+
+    ExitInfo1 = InstructionData->Ext.RmData;
+    ExitInfo2 = Bytes;
+
+    Ghcb->SaveArea.SwScratch = (UINT64) Ghcb->SharedBuffer;
+    Status = VmgExit (Ghcb, SVM_EXIT_MMIO_READ, ExitInfo1, ExitInfo2);
+    if (Status != 0) {
+      return Status;
+    }
+
+    Register = GetRegisterPointer (Regs, InstructionData->Ext.ModRm.Reg);
+    if (Bytes == 4) {
+      //
+      // Zero-extend for 32-bit operation
+      //
+      *Register = 0;
+    }
+    CopyMem (Register, Ghcb->SharedBuffer, Bytes);
+    break;
+
+  //
+  // MMIO read w/ zero-extension ((MOVZX regX, reg/memX)
+  //
+  case 0xB6:
+    Bytes = 1;
+    //
+    // fall through
+    //
+  case 0xB7:
+    Bytes = (Bytes != 0) ? Bytes : 2;
+
+    ExitInfo1 = InstructionData->Ext.RmData;
+    ExitInfo2 = Bytes;
+
+    Ghcb->SaveArea.SwScratch = (UINT64) Ghcb->SharedBuffer;
+    Status = VmgExit (Ghcb, SVM_EXIT_MMIO_READ, ExitInfo1, ExitInfo2);
+    if (Status != 0) {
+      return Status;
+    }
+
+    Register = GetRegisterPointer (Regs, InstructionData->Ext.ModRm.Reg);
+    SetMem (Register, InstructionData->DataSize, 0);
+    CopyMem (Register, Ghcb->SharedBuffer, Bytes);
+    break;
+
+  //
+  // MMIO read w/ sign-extension (MOVSX regX, reg/memX)
+  //
+  case 0xBE:
+    Bytes = 1;
+    //
+    // fall through
+    //
+  case 0xBF:
+    Bytes = (Bytes != 0) ? Bytes : 2;
+
+    ExitInfo1 = InstructionData->Ext.RmData;
+    ExitInfo2 = Bytes;
+
+    Ghcb->SaveArea.SwScratch = (UINT64) Ghcb->SharedBuffer;
+    Status = VmgExit (Ghcb, SVM_EXIT_MMIO_READ, ExitInfo1, ExitInfo2);
+    if (Status != 0) {
+      return Status;
+    }
+
+    if (Bytes == 1) {
+      UINT8 *Data = (UINT8 *) Ghcb->SharedBuffer;
+
+      SignByte = ((*Data & BIT7) != 0) ? 0xFF : 0x00;
+    } else {
+      UINT16 *Data = (UINT16 *) Ghcb->SharedBuffer;
+
+      SignByte = ((*Data & BIT15) != 0) ? 0xFF : 0x00;
+    }
+
+    Register = GetRegisterPointer (Regs, InstructionData->Ext.ModRm.Reg);
+    SetMem (Register, InstructionData->DataSize, SignByte);
+    CopyMem (Register, Ghcb->SharedBuffer, Bytes);
+    break;
+
+  default:
+    Status = GP_EXCEPTION;
+    ASSERT (FALSE);
+  }
+
+  return Status;
+}
+
 /**
   Handle an MSR event.
 
@@ -770,6 +1252,10 @@ VmgExitHandleVc (
     NaeExit = MsrExit;
     break;
 
+  case SVM_EXIT_NPF:
+    NaeExit = MmioExit;
+    break;
+
   default:
     NaeExit = UnsupportedExit;
   }
-- 
2.27.0


-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.

View/Reply Online (#63859): https://edk2.groups.io/g/devel/message/63859
Mute This Topic: https://groups.io/mt/76056519/1813853
Group Owner: devel+owner at edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub  [edk2-devel-archive at redhat.com]
-=-=-=-=-=-=-=-=-=-=-=-





More information about the edk2-devel-archive mailing list