x86/vmware: Introduce VMware hypercall API
Introduce a vmware_hypercall family of functions. It is a common implementation
to be used by the VMware guest code and virtual device drivers in architecture
independent manner.
The API consists of vmware_hypercallX and vmware_hypercall_hb_{out,in}
set of functions analogous to KVM's hypercall API. Architecture-specific
implementation is hidden inside.
It will simplify future enhancements in VMware hypercalls such as SEV-ES and
TDX related changes without needs to modify a caller in device drivers code.
Current implementation extends an idea from
bac7b4e843 ("x86/vmware: Update platform detection code for VMCALL/VMMCALL hypercalls")
to have a slow, but safe path vmware_hypercall_slow() earlier during the boot
when alternatives are not yet applied. The code inherits VMWARE_CMD logic from
the commit mentioned above.
Move common macros from vmware.c to vmware.h.
[ bp: Fold in a fix:
https://lore.kernel.org/r/20240625083348.2299-1-alexey.makhalov@broadcom.com ]
Signed-off-by: Alexey Makhalov <alexey.makhalov@broadcom.com>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Link: https://lore.kernel.org/r/20240613191650.9913-2-alexey.makhalov@broadcom.com
This commit is contained in:
committed by
Borislav Petkov (AMD)
parent
f2661062f1
commit
34bf25e820
+265
-14
@@ -7,26 +7,277 @@
|
||||
#include <linux/stringify.h>
|
||||
|
||||
/*
|
||||
* The hypercall definitions differ in the low word of the %edx argument
|
||||
* in the following way: the old port base interface uses the port
|
||||
* number to distinguish between high- and low bandwidth versions.
|
||||
* VMware hypercall ABI.
|
||||
*
|
||||
* - Low bandwidth (LB) hypercalls (I/O port based, vmcall and vmmcall)
|
||||
* have up to 6 input and 6 output arguments passed and returned using
|
||||
* registers: %eax (arg0), %ebx (arg1), %ecx (arg2), %edx (arg3),
|
||||
* %esi (arg4), %edi (arg5).
|
||||
* The following input arguments must be initialized by the caller:
|
||||
* arg0 - VMWARE_HYPERVISOR_MAGIC
|
||||
* arg2 - Hypercall command
|
||||
* arg3 bits [15:0] - Port number, LB and direction flags
|
||||
*
|
||||
* - High bandwidth (HB) hypercalls are I/O port based only. They have
|
||||
* up to 7 input and 7 output arguments passed and returned using
|
||||
* registers: %eax (arg0), %ebx (arg1), %ecx (arg2), %edx (arg3),
|
||||
* %esi (arg4), %edi (arg5), %ebp (arg6).
|
||||
* The following input arguments must be initialized by the caller:
|
||||
* arg0 - VMWARE_HYPERVISOR_MAGIC
|
||||
* arg1 - Hypercall command
|
||||
* arg3 bits [15:0] - Port number, HB and direction flags
|
||||
*
|
||||
* For compatibility purposes, x86_64 systems use only lower 32 bits
|
||||
* for input and output arguments.
|
||||
*
|
||||
* The hypercall definitions differ in the low word of the %edx (arg3)
|
||||
* in the following way: the old I/O port based interface uses the port
|
||||
* number to distinguish between high- and low bandwidth versions, and
|
||||
* uses IN/OUT instructions to define transfer direction.
|
||||
*
|
||||
* The new vmcall interface instead uses a set of flags to select
|
||||
* bandwidth mode and transfer direction. The flags should be loaded
|
||||
* into %dx by any user and are automatically replaced by the port
|
||||
* number if the VMWARE_HYPERVISOR_PORT method is used.
|
||||
*
|
||||
* In short, new driver code should strictly use the new definition of
|
||||
* %dx content.
|
||||
* into arg3 by any user and are automatically replaced by the port
|
||||
* number if the I/O port method is used.
|
||||
*/
|
||||
|
||||
/* Old port-based version */
|
||||
#define VMWARE_HYPERVISOR_PORT 0x5658
|
||||
#define VMWARE_HYPERVISOR_PORT_HB 0x5659
|
||||
#define VMWARE_HYPERVISOR_HB BIT(0)
|
||||
#define VMWARE_HYPERVISOR_OUT BIT(1)
|
||||
|
||||
/* Current vmcall / vmmcall version */
|
||||
#define VMWARE_HYPERVISOR_HB BIT(0)
|
||||
#define VMWARE_HYPERVISOR_OUT BIT(1)
|
||||
#define VMWARE_HYPERVISOR_PORT 0x5658
|
||||
#define VMWARE_HYPERVISOR_PORT_HB (VMWARE_HYPERVISOR_PORT | \
|
||||
VMWARE_HYPERVISOR_HB)
|
||||
|
||||
#define VMWARE_HYPERVISOR_MAGIC 0x564d5868U
|
||||
|
||||
#define VMWARE_CMD_GETVERSION 10
|
||||
#define VMWARE_CMD_GETHZ 45
|
||||
#define VMWARE_CMD_GETVCPU_INFO 68
|
||||
#define VMWARE_CMD_STEALCLOCK 91
|
||||
|
||||
#define CPUID_VMWARE_FEATURES_ECX_VMMCALL BIT(0)
|
||||
#define CPUID_VMWARE_FEATURES_ECX_VMCALL BIT(1)
|
||||
|
||||
extern unsigned long vmware_hypercall_slow(unsigned long cmd,
|
||||
unsigned long in1, unsigned long in3,
|
||||
unsigned long in4, unsigned long in5,
|
||||
u32 *out1, u32 *out2, u32 *out3,
|
||||
u32 *out4, u32 *out5);
|
||||
|
||||
/*
|
||||
* The low bandwidth call. The low word of %edx is presumed to have OUT bit
|
||||
* set. The high word of %edx may contain input data from the caller.
|
||||
*/
|
||||
#define VMWARE_HYPERCALL \
|
||||
ALTERNATIVE_2("movw %[port], %%dx\n\t" \
|
||||
"inl (%%dx), %%eax", \
|
||||
"vmcall", X86_FEATURE_VMCALL, \
|
||||
"vmmcall", X86_FEATURE_VMW_VMMCALL)
|
||||
|
||||
static inline
|
||||
unsigned long vmware_hypercall1(unsigned long cmd, unsigned long in1)
|
||||
{
|
||||
unsigned long out0;
|
||||
|
||||
if (unlikely(!alternatives_patched) && !__is_defined(MODULE))
|
||||
return vmware_hypercall_slow(cmd, in1, 0, 0, 0,
|
||||
NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
asm_inline volatile (VMWARE_HYPERCALL
|
||||
: "=a" (out0)
|
||||
: [port] "i" (VMWARE_HYPERVISOR_PORT),
|
||||
"a" (VMWARE_HYPERVISOR_MAGIC),
|
||||
"b" (in1),
|
||||
"c" (cmd),
|
||||
"d" (0)
|
||||
: "cc", "memory");
|
||||
return out0;
|
||||
}
|
||||
|
||||
static inline
|
||||
unsigned long vmware_hypercall3(unsigned long cmd, unsigned long in1,
|
||||
u32 *out1, u32 *out2)
|
||||
{
|
||||
unsigned long out0;
|
||||
|
||||
if (unlikely(!alternatives_patched) && !__is_defined(MODULE))
|
||||
return vmware_hypercall_slow(cmd, in1, 0, 0, 0,
|
||||
out1, out2, NULL, NULL, NULL);
|
||||
|
||||
asm_inline volatile (VMWARE_HYPERCALL
|
||||
: "=a" (out0), "=b" (*out1), "=c" (*out2)
|
||||
: [port] "i" (VMWARE_HYPERVISOR_PORT),
|
||||
"a" (VMWARE_HYPERVISOR_MAGIC),
|
||||
"b" (in1),
|
||||
"c" (cmd),
|
||||
"d" (0)
|
||||
: "cc", "memory");
|
||||
return out0;
|
||||
}
|
||||
|
||||
static inline
|
||||
unsigned long vmware_hypercall4(unsigned long cmd, unsigned long in1,
|
||||
u32 *out1, u32 *out2, u32 *out3)
|
||||
{
|
||||
unsigned long out0;
|
||||
|
||||
if (unlikely(!alternatives_patched) && !__is_defined(MODULE))
|
||||
return vmware_hypercall_slow(cmd, in1, 0, 0, 0,
|
||||
out1, out2, out3, NULL, NULL);
|
||||
|
||||
asm_inline volatile (VMWARE_HYPERCALL
|
||||
: "=a" (out0), "=b" (*out1), "=c" (*out2), "=d" (*out3)
|
||||
: [port] "i" (VMWARE_HYPERVISOR_PORT),
|
||||
"a" (VMWARE_HYPERVISOR_MAGIC),
|
||||
"b" (in1),
|
||||
"c" (cmd),
|
||||
"d" (0)
|
||||
: "cc", "memory");
|
||||
return out0;
|
||||
}
|
||||
|
||||
static inline
|
||||
unsigned long vmware_hypercall5(unsigned long cmd, unsigned long in1,
|
||||
unsigned long in3, unsigned long in4,
|
||||
unsigned long in5, u32 *out2)
|
||||
{
|
||||
unsigned long out0;
|
||||
|
||||
if (unlikely(!alternatives_patched) && !__is_defined(MODULE))
|
||||
return vmware_hypercall_slow(cmd, in1, in3, in4, in5,
|
||||
NULL, out2, NULL, NULL, NULL);
|
||||
|
||||
asm_inline volatile (VMWARE_HYPERCALL
|
||||
: "=a" (out0), "=c" (*out2)
|
||||
: [port] "i" (VMWARE_HYPERVISOR_PORT),
|
||||
"a" (VMWARE_HYPERVISOR_MAGIC),
|
||||
"b" (in1),
|
||||
"c" (cmd),
|
||||
"d" (in3),
|
||||
"S" (in4),
|
||||
"D" (in5)
|
||||
: "cc", "memory");
|
||||
return out0;
|
||||
}
|
||||
|
||||
static inline
|
||||
unsigned long vmware_hypercall6(unsigned long cmd, unsigned long in1,
|
||||
unsigned long in3, u32 *out2,
|
||||
u32 *out3, u32 *out4, u32 *out5)
|
||||
{
|
||||
unsigned long out0;
|
||||
|
||||
if (unlikely(!alternatives_patched) && !__is_defined(MODULE))
|
||||
return vmware_hypercall_slow(cmd, in1, in3, 0, 0,
|
||||
NULL, out2, out3, out4, out5);
|
||||
|
||||
asm_inline volatile (VMWARE_HYPERCALL
|
||||
: "=a" (out0), "=c" (*out2), "=d" (*out3), "=S" (*out4),
|
||||
"=D" (*out5)
|
||||
: [port] "i" (VMWARE_HYPERVISOR_PORT),
|
||||
"a" (VMWARE_HYPERVISOR_MAGIC),
|
||||
"b" (in1),
|
||||
"c" (cmd),
|
||||
"d" (in3)
|
||||
: "cc", "memory");
|
||||
return out0;
|
||||
}
|
||||
|
||||
static inline
|
||||
unsigned long vmware_hypercall7(unsigned long cmd, unsigned long in1,
|
||||
unsigned long in3, unsigned long in4,
|
||||
unsigned long in5, u32 *out1,
|
||||
u32 *out2, u32 *out3)
|
||||
{
|
||||
unsigned long out0;
|
||||
|
||||
if (unlikely(!alternatives_patched) && !__is_defined(MODULE))
|
||||
return vmware_hypercall_slow(cmd, in1, in3, in4, in5,
|
||||
out1, out2, out3, NULL, NULL);
|
||||
|
||||
asm_inline volatile (VMWARE_HYPERCALL
|
||||
: "=a" (out0), "=b" (*out1), "=c" (*out2), "=d" (*out3)
|
||||
: [port] "i" (VMWARE_HYPERVISOR_PORT),
|
||||
"a" (VMWARE_HYPERVISOR_MAGIC),
|
||||
"b" (in1),
|
||||
"c" (cmd),
|
||||
"d" (in3),
|
||||
"S" (in4),
|
||||
"D" (in5)
|
||||
: "cc", "memory");
|
||||
return out0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_X86_64
|
||||
#define VMW_BP_CONSTRAINT "r"
|
||||
#else
|
||||
#define VMW_BP_CONSTRAINT "m"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* High bandwidth calls are not supported on encrypted memory guests.
|
||||
* The caller should check cc_platform_has(CC_ATTR_MEM_ENCRYPT) and use
|
||||
* low bandwidth hypercall if memory encryption is set.
|
||||
* This assumption simplifies HB hypercall implementation to just I/O port
|
||||
* based approach without alternative patching.
|
||||
*/
|
||||
static inline
|
||||
unsigned long vmware_hypercall_hb_out(unsigned long cmd, unsigned long in2,
|
||||
unsigned long in3, unsigned long in4,
|
||||
unsigned long in5, unsigned long in6,
|
||||
u32 *out1)
|
||||
{
|
||||
unsigned long out0;
|
||||
|
||||
asm_inline volatile (
|
||||
UNWIND_HINT_SAVE
|
||||
"push %%" _ASM_BP "\n\t"
|
||||
UNWIND_HINT_UNDEFINED
|
||||
"mov %[in6], %%" _ASM_BP "\n\t"
|
||||
"rep outsb\n\t"
|
||||
"pop %%" _ASM_BP "\n\t"
|
||||
UNWIND_HINT_RESTORE
|
||||
: "=a" (out0), "=b" (*out1)
|
||||
: "a" (VMWARE_HYPERVISOR_MAGIC),
|
||||
"b" (cmd),
|
||||
"c" (in2),
|
||||
"d" (in3 | VMWARE_HYPERVISOR_PORT_HB),
|
||||
"S" (in4),
|
||||
"D" (in5),
|
||||
[in6] VMW_BP_CONSTRAINT (in6)
|
||||
: "cc", "memory");
|
||||
return out0;
|
||||
}
|
||||
|
||||
static inline
|
||||
unsigned long vmware_hypercall_hb_in(unsigned long cmd, unsigned long in2,
|
||||
unsigned long in3, unsigned long in4,
|
||||
unsigned long in5, unsigned long in6,
|
||||
u32 *out1)
|
||||
{
|
||||
unsigned long out0;
|
||||
|
||||
asm_inline volatile (
|
||||
UNWIND_HINT_SAVE
|
||||
"push %%" _ASM_BP "\n\t"
|
||||
UNWIND_HINT_UNDEFINED
|
||||
"mov %[in6], %%" _ASM_BP "\n\t"
|
||||
"rep insb\n\t"
|
||||
"pop %%" _ASM_BP "\n\t"
|
||||
UNWIND_HINT_RESTORE
|
||||
: "=a" (out0), "=b" (*out1)
|
||||
: "a" (VMWARE_HYPERVISOR_MAGIC),
|
||||
"b" (cmd),
|
||||
"c" (in2),
|
||||
"d" (in3 | VMWARE_HYPERVISOR_PORT_HB),
|
||||
"S" (in4),
|
||||
"D" (in5),
|
||||
[in6] VMW_BP_CONSTRAINT (in6)
|
||||
: "cc", "memory");
|
||||
return out0;
|
||||
}
|
||||
#undef VMW_BP_CONSTRAINT
|
||||
#undef VMWARE_HYPERCALL
|
||||
|
||||
/* The low bandwidth call. The low word of edx is presumed clear. */
|
||||
#define VMWARE_HYPERCALL \
|
||||
|
||||
@@ -41,17 +41,9 @@
|
||||
|
||||
#define CPUID_VMWARE_INFO_LEAF 0x40000000
|
||||
#define CPUID_VMWARE_FEATURES_LEAF 0x40000010
|
||||
#define CPUID_VMWARE_FEATURES_ECX_VMMCALL BIT(0)
|
||||
#define CPUID_VMWARE_FEATURES_ECX_VMCALL BIT(1)
|
||||
|
||||
#define VMWARE_HYPERVISOR_MAGIC 0x564D5868
|
||||
|
||||
#define VMWARE_CMD_GETVERSION 10
|
||||
#define VMWARE_CMD_GETHZ 45
|
||||
#define VMWARE_CMD_GETVCPU_INFO 68
|
||||
#define VMWARE_CMD_LEGACY_X2APIC 3
|
||||
#define VMWARE_CMD_VCPU_RESERVED 31
|
||||
#define VMWARE_CMD_STEALCLOCK 91
|
||||
|
||||
#define STEALCLOCK_NOT_AVAILABLE (-1)
|
||||
#define STEALCLOCK_DISABLED 0
|
||||
@@ -110,6 +102,68 @@ struct vmware_steal_time {
|
||||
static unsigned long vmware_tsc_khz __ro_after_init;
|
||||
static u8 vmware_hypercall_mode __ro_after_init;
|
||||
|
||||
unsigned long vmware_hypercall_slow(unsigned long cmd,
|
||||
unsigned long in1, unsigned long in3,
|
||||
unsigned long in4, unsigned long in5,
|
||||
u32 *out1, u32 *out2, u32 *out3,
|
||||
u32 *out4, u32 *out5)
|
||||
{
|
||||
unsigned long out0, rbx, rcx, rdx, rsi, rdi;
|
||||
|
||||
switch (vmware_hypercall_mode) {
|
||||
case CPUID_VMWARE_FEATURES_ECX_VMCALL:
|
||||
asm_inline volatile ("vmcall"
|
||||
: "=a" (out0), "=b" (rbx), "=c" (rcx),
|
||||
"=d" (rdx), "=S" (rsi), "=D" (rdi)
|
||||
: "a" (VMWARE_HYPERVISOR_MAGIC),
|
||||
"b" (in1),
|
||||
"c" (cmd),
|
||||
"d" (in3),
|
||||
"S" (in4),
|
||||
"D" (in5)
|
||||
: "cc", "memory");
|
||||
break;
|
||||
case CPUID_VMWARE_FEATURES_ECX_VMMCALL:
|
||||
asm_inline volatile ("vmmcall"
|
||||
: "=a" (out0), "=b" (rbx), "=c" (rcx),
|
||||
"=d" (rdx), "=S" (rsi), "=D" (rdi)
|
||||
: "a" (VMWARE_HYPERVISOR_MAGIC),
|
||||
"b" (in1),
|
||||
"c" (cmd),
|
||||
"d" (in3),
|
||||
"S" (in4),
|
||||
"D" (in5)
|
||||
: "cc", "memory");
|
||||
break;
|
||||
default:
|
||||
asm_inline volatile ("movw %[port], %%dx; inl (%%dx), %%eax"
|
||||
: "=a" (out0), "=b" (rbx), "=c" (rcx),
|
||||
"=d" (rdx), "=S" (rsi), "=D" (rdi)
|
||||
: [port] "i" (VMWARE_HYPERVISOR_PORT),
|
||||
"a" (VMWARE_HYPERVISOR_MAGIC),
|
||||
"b" (in1),
|
||||
"c" (cmd),
|
||||
"d" (in3),
|
||||
"S" (in4),
|
||||
"D" (in5)
|
||||
: "cc", "memory");
|
||||
break;
|
||||
}
|
||||
|
||||
if (out1)
|
||||
*out1 = rbx;
|
||||
if (out2)
|
||||
*out2 = rcx;
|
||||
if (out3)
|
||||
*out3 = rdx;
|
||||
if (out4)
|
||||
*out4 = rsi;
|
||||
if (out5)
|
||||
*out5 = rdi;
|
||||
|
||||
return out0;
|
||||
}
|
||||
|
||||
static inline int __vmware_platform(void)
|
||||
{
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
|
||||
Reference in New Issue
Block a user