linux-loongson/arch/x86/virt/vmx/tdx/tdxcall.S
Kai Huang 7b804135d4 x86/virt/tdx: Make TDX_MODULE_CALL handle SEAMCALL #UD and #GP
SEAMCALL instruction causes #UD if the CPU isn't in VMX operation.
Currently the TDX_MODULE_CALL assembly doesn't handle #UD, thus making
SEAMCALL when VMX is disabled would cause Oops.

Unfortunately, there are legal cases that SEAMCALL can be made when VMX
is disabled.  For instance, VMX can be disabled due to emergency reboot
while there are still TDX guests running.

Extend the TDX_MODULE_CALL assembly to return an error code for #UD to
handle this case gracefully, e.g., KVM can then quietly eat all SEAMCALL
errors caused by emergency reboot.

SEAMCALL instruction also causes #GP when TDX isn't enabled by the BIOS.
Use _ASM_EXTABLE_FAULT() to catch both exceptions with the trap number
recorded, and define two new error codes by XORing the trap number to
the TDX_SW_ERROR.  This opportunistically handles #GP too while using
the same simple assembly code.

A bonus is when kernel mistakenly calls SEAMCALL when CPU isn't in VMX
operation, or when TDX isn't enabled by the BIOS, or when the BIOS is
buggy, the kernel can get a nicer error code rather than a less
understandable Oops.

This is basically based on Peter's code.

Suggested-by: Peter Zijlstra <peterz@infradead.org>
Signed-off-by: Kai Huang <kai.huang@intel.com>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Reviewed-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lore.kernel.org/all/de975832a367f476aab2d0eb0d9de66019a16b54.1692096753.git.kai.huang%40intel.com
2023-09-12 16:30:27 -07:00

221 lines
6.1 KiB
ArmAsm

/* SPDX-License-Identifier: GPL-2.0 */
#include <asm/asm-offsets.h>
#include <asm/frame.h>
#include <asm/asm.h>
#include <asm/tdx.h>
/*
* TDCALL and SEAMCALL are supported in Binutils >= 2.36.
*/
#define tdcall .byte 0x66,0x0f,0x01,0xcc
#define seamcall .byte 0x66,0x0f,0x01,0xcf
/*
* TDX_MODULE_CALL - common helper macro for both
* TDCALL and SEAMCALL instructions.
*
* TDCALL - used by TDX guests to make requests to the
* TDX module and hypercalls to the VMM.
* SEAMCALL - used by TDX hosts to make requests to the
* TDX module.
*
*-------------------------------------------------------------------------
* TDCALL/SEAMCALL ABI:
*-------------------------------------------------------------------------
* Input Registers:
*
* RAX - TDCALL/SEAMCALL Leaf number.
* RCX,RDX,RDI,RSI,RBX,R8-R15 - TDCALL/SEAMCALL Leaf specific input registers.
*
* Output Registers:
*
* RAX - TDCALL/SEAMCALL instruction error code.
* RCX,RDX,RDI,RSI,RBX,R8-R15 - TDCALL/SEAMCALL Leaf specific output registers.
*
*-------------------------------------------------------------------------
*
* So while the common core (RAX,RCX,RDX,R8-R11) fits nicely in the
* callee-clobbered registers and even leaves RDI,RSI free to act as a
* base pointer, some leafs (e.g., VP.ENTER) make a giant mess of things.
*
* For simplicity, assume that anything that needs the callee-saved regs
* also tramples on RDI,RSI. This isn't strictly true, see for example
* TDH.EXPORT.MEM.
*/
.macro TDX_MODULE_CALL host:req ret=0 saved=0
FRAME_BEGIN
/* Move Leaf ID to RAX */
mov %rdi, %rax
/* Move other input regs from 'struct tdx_module_args' */
movq TDX_MODULE_rcx(%rsi), %rcx
movq TDX_MODULE_rdx(%rsi), %rdx
movq TDX_MODULE_r8(%rsi), %r8
movq TDX_MODULE_r9(%rsi), %r9
movq TDX_MODULE_r10(%rsi), %r10
movq TDX_MODULE_r11(%rsi), %r11
.if \saved
/*
* Move additional input regs from the structure. For simplicity
* assume that anything needs the callee-saved regs also tramples
* on RDI/RSI (see VP.ENTER).
*/
/* Save those callee-saved GPRs as mandated by the x86_64 ABI */
pushq %rbx
pushq %r12
pushq %r13
pushq %r14
pushq %r15
movq TDX_MODULE_r12(%rsi), %r12
movq TDX_MODULE_r13(%rsi), %r13
movq TDX_MODULE_r14(%rsi), %r14
movq TDX_MODULE_r15(%rsi), %r15
movq TDX_MODULE_rbx(%rsi), %rbx
.if \ret
/* Save the structure pointer as RSI is about to be clobbered */
pushq %rsi
.endif
movq TDX_MODULE_rdi(%rsi), %rdi
/* RSI needs to be done at last */
movq TDX_MODULE_rsi(%rsi), %rsi
.endif /* \saved */
.if \host
.Lseamcall\@:
seamcall
/*
* SEAMCALL instruction is essentially a VMExit from VMX root
* mode to SEAM VMX root mode. VMfailInvalid (CF=1) indicates
* that the targeted SEAM firmware is not loaded or disabled,
* or P-SEAMLDR is busy with another SEAMCALL. %rax is not
* changed in this case.
*
* Set %rax to TDX_SEAMCALL_VMFAILINVALID for VMfailInvalid.
* This value will never be used as actual SEAMCALL error code as
* it is from the Reserved status code class.
*/
jc .Lseamcall_vmfailinvalid\@
.else
tdcall
.endif
.if \ret
.if \saved
/*
* Restore the structure from stack to save the output registers
*
* In case of VP.ENTER returns due to TDVMCALL, all registers are
* valid thus no register can be used as spare to restore the
* structure from the stack (see "TDH.VP.ENTER Output Operands
* Definition on TDCALL(TDG.VP.VMCALL) Following a TD Entry").
* For this case, need to make one register as spare by saving it
* to the stack and then manually load the structure pointer to
* the spare register.
*
* Note for other TDCALLs/SEAMCALLs there are spare registers
* thus no need for such hack but just use this for all.
*/
pushq %rax /* save the TDCALL/SEAMCALL return code */
movq 8(%rsp), %rax /* restore the structure pointer */
movq %rsi, TDX_MODULE_rsi(%rax) /* save RSI */
popq %rax /* restore the return code */
popq %rsi /* pop the structure pointer */
/* Copy additional output regs to the structure */
movq %r12, TDX_MODULE_r12(%rsi)
movq %r13, TDX_MODULE_r13(%rsi)
movq %r14, TDX_MODULE_r14(%rsi)
movq %r15, TDX_MODULE_r15(%rsi)
movq %rbx, TDX_MODULE_rbx(%rsi)
movq %rdi, TDX_MODULE_rdi(%rsi)
.endif /* \saved */
/* Copy output registers to the structure */
movq %rcx, TDX_MODULE_rcx(%rsi)
movq %rdx, TDX_MODULE_rdx(%rsi)
movq %r8, TDX_MODULE_r8(%rsi)
movq %r9, TDX_MODULE_r9(%rsi)
movq %r10, TDX_MODULE_r10(%rsi)
movq %r11, TDX_MODULE_r11(%rsi)
.endif /* \ret */
.if \saved && \ret
/*
* Clear registers shared by guest for VP.VMCALL/VP.ENTER to prevent
* speculative use of guest's/VMM's values, including those are
* restored from the stack.
*
* See arch/x86/kvm/vmx/vmenter.S:
*
* In theory, a L1 cache miss when restoring register from stack
* could lead to speculative execution with guest's values.
*
* Note: RBP/RSP are not used as shared register. RSI has been
* restored already.
*
* XOR is cheap, thus unconditionally do for all leafs.
*/
xorl %ecx, %ecx
xorl %edx, %edx
xorl %r8d, %r8d
xorl %r9d, %r9d
xorl %r10d, %r10d
xorl %r11d, %r11d
xorl %r12d, %r12d
xorl %r13d, %r13d
xorl %r14d, %r14d
xorl %r15d, %r15d
xorl %ebx, %ebx
xorl %edi, %edi
.endif /* \ret && \host */
.if \host
.Lout\@:
.endif
.if \saved
/* Restore callee-saved GPRs as mandated by the x86_64 ABI */
popq %r15
popq %r14
popq %r13
popq %r12
popq %rbx
.endif /* \saved */
FRAME_END
RET
.if \host
.Lseamcall_vmfailinvalid\@:
mov $TDX_SEAMCALL_VMFAILINVALID, %rax
jmp .Lseamcall_fail\@
.Lseamcall_trap\@:
/*
* SEAMCALL caused #GP or #UD. By reaching here RAX contains
* the trap number. Convert the trap number to the TDX error
* code by setting TDX_SW_ERROR to the high 32-bits of RAX.
*
* Note cannot OR TDX_SW_ERROR directly to RAX as OR instruction
* only accepts 32-bit immediate at most.
*/
movq $TDX_SW_ERROR, %rdi
orq %rdi, %rax
.Lseamcall_fail\@:
.if \ret && \saved
/* pop the unused structure pointer back to RSI */
popq %rsi
.endif
jmp .Lout\@
_ASM_EXTABLE_FAULT(.Lseamcall\@, .Lseamcall_trap\@)
.endif /* \host */
.endm