Introduce 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 by analogy with KVM 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 commit bac7b4e84323
("x86/vmware: Update platform detection code for VMCALL/VMMCALL
hypercalls") to have a slow, but safe path in VMWARE_HYPERCALL
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.

Make vmware_hypercall_mode a global variable.

Signed-off-by: Alexey Makhalov <alexey.makha...@broadcom.com>
---
 arch/x86/include/asm/vmware.h | 274 ++++++++++++++++++++++++++++++++--
 arch/x86/kernel/cpu/vmware.c  |  11 +-
 2 files changed, 262 insertions(+), 23 deletions(-)

diff --git a/arch/x86/include/asm/vmware.h b/arch/x86/include/asm/vmware.h
index ac9fc51e2b18..5114f4c75c54 100644
--- a/arch/x86/include/asm/vmware.h
+++ b/arch/x86/include/asm/vmware.h
@@ -7,26 +7,272 @@
 #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.
+ */
+
+#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 u8 vmware_hypercall_mode;
+
+/*
+ * 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_3("",                                               \
+                     "jmp .Lport_call%=", X86_FEATURE_HYPERVISOR,      \
+                     "jmp .Lvmcall%=", X86_FEATURE_VMCALL,             \
+                     "vmmcall\n\t"                                     \
+                     "jmp .Lend%=", X86_FEATURE_VMW_VMMCALL)           \
+                     "cmpb $"                                          \
+                       __stringify(CPUID_VMWARE_FEATURES_ECX_VMMCALL)  \
+                       ", %[mode]\n\t"                                 \
+                     "jg .Lvmcall%=\n\t"                               \
+                     "je .Lvmmcall%=\n\t"                              \
+                     ".Lport_call%=: movw %[port], %%dx\n\t"           \
+                     "inl (%%dx), %%eax\n\t"                           \
+                     "jmp .Lend%=\n\t"                                 \
+                     ".Lvmmcall%=: vmmcall\n\t"                        \
+                     "jmp .Lend%=\n\t"                                 \
+                     ".Lvmcall%=: vmcall\n\t"                          \
+                     ".Lend%=:"
+
+static inline
+unsigned long vmware_hypercall1(unsigned long cmd, unsigned long in1)
+{
+       unsigned long out0;
+
+       asm_inline volatile (VMWARE_HYPERCALL
+               : "=a" (out0)
+               : [port] "i" (VMWARE_HYPERVISOR_PORT),
+                 [mode] "m" (vmware_hypercall_mode),
+                 "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;
+
+       asm_inline volatile (VMWARE_HYPERCALL
+               : "=a" (out0), "=b" (*out1), "=c" (*out2)
+               : [port] "i" (VMWARE_HYPERVISOR_PORT),
+                 [mode] "m" (vmware_hypercall_mode),
+                 "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;
+
+       asm_inline volatile (VMWARE_HYPERCALL
+               : "=a" (out0), "=b" (*out1), "=c" (*out2), "=d" (*out3)
+               : [port] "i" (VMWARE_HYPERVISOR_PORT),
+                 [mode] "m" (vmware_hypercall_mode),
+                 "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;
+
+       asm_inline volatile (VMWARE_HYPERCALL
+               : "=a" (out0), "=c" (*out2)
+               : [port] "i" (VMWARE_HYPERVISOR_PORT),
+                 [mode] "m" (vmware_hypercall_mode),
+                 "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;
+
+       asm_inline volatile (VMWARE_HYPERCALL
+               : "=a" (out0), "=c" (*out2), "=d" (*out3), "=S" (*out4),
+                 "=D" (*out5)
+               : [port] "i" (VMWARE_HYPERVISOR_PORT),
+                 [mode] "m" (vmware_hypercall_mode),
+                 "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;
+
+       asm_inline volatile (VMWARE_HYPERCALL
+               : "=a" (out0), "=b" (*out1), "=c" (*out2), "=d" (*out3)
+               : [port] "i" (VMWARE_HYPERVISOR_PORT),
+                 [mode] "m" (vmware_hypercall_mode),
+                 "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_REG "%%rbp"
+#define VMW_BP_CONSTRAINT "r"
+#else
+#define VMW_BP_REG "%%ebp"
+#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 " VMW_BP_REG "\n\t"
+               UNWIND_HINT_UNDEFINED
+               "mov %[in6], " VMW_BP_REG "\n\t"
+               "rep outsb\n\t"
+               "pop " VMW_BP_REG "\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;
+}
 
-/* Old port-based version */
-#define VMWARE_HYPERVISOR_PORT    0x5658
-#define VMWARE_HYPERVISOR_PORT_HB 0x5659
+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;
 
-/* Current vmcall / vmmcall version */
-#define VMWARE_HYPERVISOR_HB   BIT(0)
-#define VMWARE_HYPERVISOR_OUT  BIT(1)
+       asm_inline volatile (
+               UNWIND_HINT_SAVE
+               "push " VMW_BP_REG "\n\t"
+               UNWIND_HINT_UNDEFINED
+               "mov %[in6], " VMW_BP_REG "\n\t"
+               "rep insb\n\t"
+               "pop " VMW_BP_REG "\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_REG
+#undef VMW_BP_CONSTRAINT
+#undef VMWARE_HYPERCALL
 
 /* The low bandwidth call. The low word of edx is presumed clear. */
 #define VMWARE_HYPERCALL                                               \
diff --git a/arch/x86/kernel/cpu/vmware.c b/arch/x86/kernel/cpu/vmware.c
index 11f83d07925e..07b7b5b773a0 100644
--- a/arch/x86/kernel/cpu/vmware.c
+++ b/arch/x86/kernel/cpu/vmware.c
@@ -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
@@ -108,7 +100,8 @@ struct vmware_steal_time {
 };
 
 static unsigned long vmware_tsc_khz __ro_after_init;
-static u8 vmware_hypercall_mode     __ro_after_init;
+u8 vmware_hypercall_mode __ro_after_init;
+EXPORT_SYMBOL_GPL(vmware_hypercall_mode);
 
 static inline int __vmware_platform(void)
 {
-- 
2.39.0

Reply via email to