2 X64 #VC Exception Handler functon.
4 Copyright (C) 2020, Advanced Micro Devices, Inc. All rights reserved.<BR>
5 SPDX-License-Identifier: BSD-2-Clause-Patent
11 #include <Library/BaseMemoryLib.h>
12 #include <Library/VmgExitLib.h>
13 #include <Register/Amd/Msr.h>
14 #include <Register/Intel/Cpuid.h>
15 #include <IndustryStandard/InstructionParsing.h>
18 // Instruction execution mode definition
24 } SEV_ES_INSTRUCTION_MODE
;
27 // Instruction size definition (for operand and address)
34 } SEV_ES_INSTRUCTION_SIZE
;
37 // Intruction segment definition
46 } SEV_ES_INSTRUCTION_SEGMENT
;
49 // Instruction rep function definition
55 } SEV_ES_INSTRUCTION_REP
;
61 } SEV_ES_INSTRUCTION_MODRM_EXT
;
67 } SEV_ES_INSTRUCTION_SIB_EXT
;
70 // Instruction opcode definition
73 SEV_ES_INSTRUCTION_MODRM_EXT ModRm
;
75 SEV_ES_INSTRUCTION_SIB_EXT Sib
;
79 } SEV_ES_INSTRUCTION_OPCODE_EXT
;
82 // Instruction parsing context definition
87 SEV_ES_INSTRUCTION_MODE Mode
;
88 SEV_ES_INSTRUCTION_SIZE DataSize
;
89 SEV_ES_INSTRUCTION_SIZE AddrSize
;
90 BOOLEAN SegmentSpecified
;
91 SEV_ES_INSTRUCTION_SEGMENT Segment
;
92 SEV_ES_INSTRUCTION_REP RepMode
;
102 INSTRUCTION_REX_PREFIX RexPrefix
;
104 BOOLEAN ModRmPresent
;
105 INSTRUCTION_MODRM ModRm
;
112 UINTN DisplacementSize
;
115 SEV_ES_INSTRUCTION_OPCODE_EXT Ext
;
116 } SEV_ES_INSTRUCTION_DATA
;
119 // Non-automatic Exit function prototype
125 EFI_SYSTEM_CONTEXT_X64
*Regs
,
126 SEV_ES_INSTRUCTION_DATA
*InstructionData
131 Checks the GHCB to determine if the specified register has been marked valid.
133 The ValidBitmap area represents the areas of the GHCB that have been marked
134 valid. Return an indication of whether the area of the GHCB that holds the
135 specified register has been marked valid.
137 @param[in] Ghcb Pointer to the Guest-Hypervisor Communication Block
138 @param[in] Reg Offset in the GHCB of the register to check
140 @retval TRUE Register has been marked vald in the GHCB
141 @retval FALSE Register has not been marked valid in the GHCB
157 return ((Ghcb
->SaveArea
.ValidBitmap
[RegIndex
] & (1 << RegBit
)) != 0);
161 Marks a register as valid in the GHCB.
163 The ValidBitmap area represents the areas of the GHCB that have been marked
164 valid. Set the area of the GHCB that holds the specified register as valid.
166 @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block
167 @param[in] Reg Offset in the GHCB of the register to mark valid
183 Ghcb
->SaveArea
.ValidBitmap
[RegIndex
] |= (1 << RegBit
);
187 Decode instruction prefixes.
189 Parse the instruction data to track the instruction prefixes that have
192 @param[in] Regs x64 processor context
193 @param[in, out] InstructionData Instruction parsing context
199 IN EFI_SYSTEM_CONTEXT_X64
*Regs
,
200 IN OUT SEV_ES_INSTRUCTION_DATA
*InstructionData
203 SEV_ES_INSTRUCTION_MODE Mode
;
204 SEV_ES_INSTRUCTION_SIZE ModeDataSize
;
205 SEV_ES_INSTRUCTION_SIZE ModeAddrSize
;
209 // Always in 64-bit mode
211 Mode
= LongMode64Bit
;
212 ModeDataSize
= Size32Bits
;
213 ModeAddrSize
= Size64Bits
;
215 InstructionData
->Mode
= Mode
;
216 InstructionData
->DataSize
= ModeDataSize
;
217 InstructionData
->AddrSize
= ModeAddrSize
;
219 InstructionData
->Prefixes
= InstructionData
->Begin
;
221 Byte
= InstructionData
->Prefixes
;
222 for ( ; ; Byte
++, InstructionData
->PrefixSize
++) {
224 // Check the 0x40 to 0x4F range using an if statement here since some
225 // compilers don't like the "case 0x40 ... 0x4F:" syntax. This avoids
226 // 16 case statements below.
228 if ((*Byte
>= REX_PREFIX_START
) && (*Byte
<= REX_PREFIX_STOP
)) {
229 InstructionData
->RexPrefix
.Uint8
= *Byte
;
230 if ((*Byte
& REX_64BIT_OPERAND_SIZE_MASK
) != 0) {
231 InstructionData
->DataSize
= Size64Bits
;
237 case OVERRIDE_SEGMENT_CS
:
238 case OVERRIDE_SEGMENT_DS
:
239 case OVERRIDE_SEGMENT_ES
:
240 case OVERRIDE_SEGMENT_SS
:
241 if (Mode
!= LongMode64Bit
) {
242 InstructionData
->SegmentSpecified
= TRUE
;
243 InstructionData
->Segment
= (*Byte
>> 3) & 3;
247 case OVERRIDE_SEGMENT_FS
:
248 case OVERRIDE_SEGMENT_GS
:
249 InstructionData
->SegmentSpecified
= TRUE
;
250 InstructionData
->Segment
= *Byte
& 7;
253 case OVERRIDE_OPERAND_SIZE
:
254 if (InstructionData
->RexPrefix
.Uint8
== 0) {
255 InstructionData
->DataSize
=
256 (Mode
== LongMode64Bit
) ? Size16Bits
:
257 (Mode
== LongModeCompat32Bit
) ? Size16Bits
:
258 (Mode
== LongModeCompat16Bit
) ? Size32Bits
: 0;
262 case OVERRIDE_ADDRESS_SIZE
:
263 InstructionData
->AddrSize
=
264 (Mode
== LongMode64Bit
) ? Size32Bits
:
265 (Mode
== LongModeCompat32Bit
) ? Size16Bits
:
266 (Mode
== LongModeCompat16Bit
) ? Size32Bits
: 0;
273 InstructionData
->RepMode
= RepZ
;
277 InstructionData
->RepMode
= RepNZ
;
281 InstructionData
->OpCodes
= Byte
;
282 InstructionData
->OpCodeSize
= (*Byte
== TWO_BYTE_OPCODE_ESCAPE
) ? 2 : 1;
284 InstructionData
->End
= Byte
+ InstructionData
->OpCodeSize
;
285 InstructionData
->Displacement
= InstructionData
->End
;
286 InstructionData
->Immediate
= InstructionData
->End
;
293 Determine instruction length
295 Return the total length of the parsed instruction.
297 @param[in] InstructionData Instruction parsing context
299 @return Length of parsed instruction
305 IN SEV_ES_INSTRUCTION_DATA
*InstructionData
308 return (UINT64
) (InstructionData
->End
- InstructionData
->Begin
);
312 Initialize the instruction parsing context.
314 Initialize the instruction parsing context, which includes decoding the
315 instruction prefixes.
317 @param[in, out] InstructionData Instruction parsing context
318 @param[in] Ghcb Pointer to the Guest-Hypervisor Communication
320 @param[in] Regs x64 processor context
325 InitInstructionData (
326 IN OUT SEV_ES_INSTRUCTION_DATA
*InstructionData
,
328 IN EFI_SYSTEM_CONTEXT_X64
*Regs
331 SetMem (InstructionData
, sizeof (*InstructionData
), 0);
332 InstructionData
->Ghcb
= Ghcb
;
333 InstructionData
->Begin
= (UINT8
*) Regs
->Rip
;
334 InstructionData
->End
= (UINT8
*) Regs
->Rip
;
336 DecodePrefixes (Regs
, InstructionData
);
340 Report an unsupported event to the hypervisor
342 Use the VMGEXIT support to report an unsupported event to the hypervisor.
344 @param[in] Ghcb Pointer to the Guest-Hypervisor Communication
346 @param[in] Regs x64 processor context
347 @param[in] InstructionData Instruction parsing context
349 @return New exception value to propagate
356 IN EFI_SYSTEM_CONTEXT_X64
*Regs
,
357 IN SEV_ES_INSTRUCTION_DATA
*InstructionData
362 Status
= VmgExit (Ghcb
, SVM_EXIT_UNSUPPORTED
, Regs
->ExceptionData
, 0);
364 GHCB_EVENT_INJECTION Event
;
367 Event
.Elements
.Vector
= GP_EXCEPTION
;
368 Event
.Elements
.Type
= GHCB_EVENT_INJECTION_TYPE_EXCEPTION
;
369 Event
.Elements
.Valid
= 1;
371 Status
= Event
.Uint64
;
380 Use the VMGEXIT instruction to handle either a RDMSR or WRMSR event.
382 @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication
384 @param[in, out] Regs x64 processor context
385 @param[in] InstructionData Instruction parsing context
387 @retval 0 Event handled successfully
388 @return New exception value to propagate
395 IN OUT EFI_SYSTEM_CONTEXT_X64
*Regs
,
396 IN SEV_ES_INSTRUCTION_DATA
*InstructionData
399 UINT64 ExitInfo1
, Status
;
403 switch (*(InstructionData
->OpCodes
+ 1)) {
406 Ghcb
->SaveArea
.Rax
= Regs
->Rax
;
407 GhcbSetRegValid (Ghcb
, GhcbRax
);
408 Ghcb
->SaveArea
.Rdx
= Regs
->Rdx
;
409 GhcbSetRegValid (Ghcb
, GhcbRdx
);
414 Ghcb
->SaveArea
.Rcx
= Regs
->Rcx
;
415 GhcbSetRegValid (Ghcb
, GhcbRcx
);
418 return UnsupportedExit (Ghcb
, Regs
, InstructionData
);
421 Status
= VmgExit (Ghcb
, SVM_EXIT_MSR
, ExitInfo1
, 0);
426 if (ExitInfo1
== 0) {
427 if (!GhcbIsRegValid (Ghcb
, GhcbRax
) ||
428 !GhcbIsRegValid (Ghcb
, GhcbRdx
)) {
429 return UnsupportedExit (Ghcb
, Regs
, InstructionData
);
431 Regs
->Rax
= Ghcb
->SaveArea
.Rax
;
432 Regs
->Rdx
= Ghcb
->SaveArea
.Rdx
;
439 Build the IOIO event information.
441 The IOIO event information identifies the type of IO operation to be performed
442 by the hypervisor. Build this information based on the instruction data.
444 @param[in] Regs x64 processor context
445 @param[in, out] InstructionData Instruction parsing context
447 @return IOIO event information value
453 IN EFI_SYSTEM_CONTEXT_X64
*Regs
,
454 IN OUT SEV_ES_INSTRUCTION_DATA
*InstructionData
461 switch (*(InstructionData
->OpCodes
)) {
467 ExitInfo
|= IOIO_TYPE_INS
;
468 ExitInfo
|= IOIO_SEG_ES
;
469 ExitInfo
|= ((Regs
->Rdx
& 0xffff) << 16);
477 ExitInfo
|= IOIO_TYPE_OUTS
;
478 ExitInfo
|= IOIO_SEG_DS
;
479 ExitInfo
|= ((Regs
->Rdx
& 0xffff) << 16);
483 // IN immediate opcodes
487 InstructionData
->ImmediateSize
= 1;
488 InstructionData
->End
++;
489 ExitInfo
|= IOIO_TYPE_IN
;
490 ExitInfo
|= ((*(InstructionData
->OpCodes
+ 1)) << 16);
494 // OUT immediate opcodes
498 InstructionData
->ImmediateSize
= 1;
499 InstructionData
->End
++;
500 ExitInfo
|= IOIO_TYPE_OUT
;
501 ExitInfo
|= ((*(InstructionData
->OpCodes
+ 1)) << 16) | IOIO_TYPE_OUT
;
505 // IN register opcodes
509 ExitInfo
|= IOIO_TYPE_IN
;
510 ExitInfo
|= ((Regs
->Rdx
& 0xffff) << 16);
514 // OUT register opcodes
518 ExitInfo
|= IOIO_TYPE_OUT
;
519 ExitInfo
|= ((Regs
->Rdx
& 0xffff) << 16);
526 switch (*(InstructionData
->OpCodes
)) {
528 // Single-byte opcodes
536 ExitInfo
|= IOIO_DATA_8
;
540 // Length determined by instruction parsing
543 ExitInfo
|= (InstructionData
->DataSize
== Size16Bits
) ? IOIO_DATA_16
547 switch (InstructionData
->AddrSize
) {
549 ExitInfo
|= IOIO_ADDR_16
;
553 ExitInfo
|= IOIO_ADDR_32
;
557 ExitInfo
|= IOIO_ADDR_64
;
564 if (InstructionData
->RepMode
!= 0) {
565 ExitInfo
|= IOIO_REP
;
572 Handle an IOIO event.
574 Use the VMGEXIT instruction to handle an IOIO event.
576 @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication
578 @param[in, out] Regs x64 processor context
579 @param[in] InstructionData Instruction parsing context
581 @retval 0 Event handled successfully
582 @return New exception value to propagate
589 IN OUT EFI_SYSTEM_CONTEXT_X64
*Regs
,
590 IN SEV_ES_INSTRUCTION_DATA
*InstructionData
593 UINT64 ExitInfo1
, ExitInfo2
, Status
;
596 ExitInfo1
= IoioExitInfo (Regs
, InstructionData
);
597 if (ExitInfo1
== 0) {
598 return UnsupportedExit (Ghcb
, Regs
, InstructionData
);
601 IsString
= ((ExitInfo1
& IOIO_TYPE_STR
) != 0) ? TRUE
: FALSE
;
603 UINTN IoBytes
, VmgExitBytes
;
604 UINTN GhcbCount
, OpCount
;
608 IoBytes
= IOIO_DATA_BYTES (ExitInfo1
);
609 GhcbCount
= sizeof (Ghcb
->SharedBuffer
) / IoBytes
;
611 OpCount
= ((ExitInfo1
& IOIO_REP
) != 0) ? Regs
->Rcx
: 1;
612 while (OpCount
!= 0) {
613 ExitInfo2
= MIN (OpCount
, GhcbCount
);
614 VmgExitBytes
= ExitInfo2
* IoBytes
;
616 if ((ExitInfo1
& IOIO_TYPE_IN
) == 0) {
617 CopyMem (Ghcb
->SharedBuffer
, (VOID
*) Regs
->Rsi
, VmgExitBytes
);
618 Regs
->Rsi
+= VmgExitBytes
;
621 Ghcb
->SaveArea
.SwScratch
= (UINT64
) Ghcb
->SharedBuffer
;
622 Status
= VmgExit (Ghcb
, SVM_EXIT_IOIO_PROT
, ExitInfo1
, ExitInfo2
);
627 if ((ExitInfo1
& IOIO_TYPE_IN
) != 0) {
628 CopyMem ((VOID
*) Regs
->Rdi
, Ghcb
->SharedBuffer
, VmgExitBytes
);
629 Regs
->Rdi
+= VmgExitBytes
;
632 if ((ExitInfo1
& IOIO_REP
) != 0) {
633 Regs
->Rcx
-= ExitInfo2
;
636 OpCount
-= ExitInfo2
;
639 if ((ExitInfo1
& IOIO_TYPE_IN
) != 0) {
640 Ghcb
->SaveArea
.Rax
= 0;
642 CopyMem (&Ghcb
->SaveArea
.Rax
, &Regs
->Rax
, IOIO_DATA_BYTES (ExitInfo1
));
644 GhcbSetRegValid (Ghcb
, GhcbRax
);
646 Status
= VmgExit (Ghcb
, SVM_EXIT_IOIO_PROT
, ExitInfo1
, 0);
651 if ((ExitInfo1
& IOIO_TYPE_IN
) != 0) {
652 if (!GhcbIsRegValid (Ghcb
, GhcbRax
)) {
653 return UnsupportedExit (Ghcb
, Regs
, InstructionData
);
655 CopyMem (&Regs
->Rax
, &Ghcb
->SaveArea
.Rax
, IOIO_DATA_BYTES (ExitInfo1
));
663 Handle a CPUID event.
665 Use the VMGEXIT instruction to handle a CPUID event.
667 @param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication
669 @param[in, out] Regs x64 processor context
670 @param[in] InstructionData Instruction parsing context
672 @retval 0 Event handled successfully
673 @return New exception value to propagate
680 IN OUT EFI_SYSTEM_CONTEXT_X64
*Regs
,
681 IN SEV_ES_INSTRUCTION_DATA
*InstructionData
686 Ghcb
->SaveArea
.Rax
= Regs
->Rax
;
687 GhcbSetRegValid (Ghcb
, GhcbRax
);
688 Ghcb
->SaveArea
.Rcx
= Regs
->Rcx
;
689 GhcbSetRegValid (Ghcb
, GhcbRcx
);
690 if (Regs
->Rax
== CPUID_EXTENDED_STATE
) {
693 Cr4
.UintN
= AsmReadCr4 ();
694 Ghcb
->SaveArea
.XCr0
= (Cr4
.Bits
.OSXSAVE
== 1) ? AsmXGetBv (0) : 1;
695 GhcbSetRegValid (Ghcb
, GhcbXCr0
);
698 Status
= VmgExit (Ghcb
, SVM_EXIT_CPUID
, 0, 0);
703 if (!GhcbIsRegValid (Ghcb
, GhcbRax
) ||
704 !GhcbIsRegValid (Ghcb
, GhcbRbx
) ||
705 !GhcbIsRegValid (Ghcb
, GhcbRcx
) ||
706 !GhcbIsRegValid (Ghcb
, GhcbRdx
)) {
707 return UnsupportedExit (Ghcb
, Regs
, InstructionData
);
709 Regs
->Rax
= Ghcb
->SaveArea
.Rax
;
710 Regs
->Rbx
= Ghcb
->SaveArea
.Rbx
;
711 Regs
->Rcx
= Ghcb
->SaveArea
.Rcx
;
712 Regs
->Rdx
= Ghcb
->SaveArea
.Rdx
;
718 Handle a #VC exception.
720 Performs the necessary processing to handle a #VC exception.
722 @param[in, out] ExceptionType Pointer to an EFI_EXCEPTION_TYPE to be set
723 as value to use on error.
724 @param[in, out] SystemContext Pointer to EFI_SYSTEM_CONTEXT
726 @retval EFI_SUCCESS Exception handled
727 @retval EFI_UNSUPPORTED #VC not supported, (new) exception value to
729 @retval EFI_PROTOCOL_ERROR #VC handling failed, (new) exception value to
736 IN OUT EFI_EXCEPTION_TYPE
*ExceptionType
,
737 IN OUT EFI_SYSTEM_CONTEXT SystemContext
740 MSR_SEV_ES_GHCB_REGISTER Msr
;
741 EFI_SYSTEM_CONTEXT_X64
*Regs
;
744 SEV_ES_INSTRUCTION_DATA InstructionData
;
745 UINT64 ExitCode
, Status
;
750 Msr
.GhcbPhysicalAddress
= AsmReadMsr64 (MSR_SEV_ES_GHCB
);
751 ASSERT (Msr
.GhcbInfo
.Function
== 0);
752 ASSERT (Msr
.Ghcb
!= 0);
754 Regs
= SystemContext
.SystemContextX64
;
759 ExitCode
= Regs
->ExceptionData
;
765 case SVM_EXIT_IOIO_PROT
:
774 NaeExit
= UnsupportedExit
;
777 InitInstructionData (&InstructionData
, Ghcb
, Regs
);
779 Status
= NaeExit (Ghcb
, Regs
, &InstructionData
);
781 Regs
->Rip
+= InstructionLength (&InstructionData
);
783 GHCB_EVENT_INJECTION Event
;
785 Event
.Uint64
= Status
;
786 if (Event
.Elements
.ErrorCodeValid
!= 0) {
787 Regs
->ExceptionData
= Event
.Elements
.ErrorCode
;
789 Regs
->ExceptionData
= 0;
792 *ExceptionType
= Event
.Elements
.Vector
;
794 VcRet
= EFI_PROTOCOL_ERROR
;