git: ecaab0fb5da4 - main - guestrpc module to handle VMware backdoor port GuestRPC functionality

From: Stephen J. Kiernan <stevek_at_FreeBSD.org>
Date: Wed, 01 May 2024 19:46:02 UTC
The branch main has been updated by stevek:

URL: https://cgit.FreeBSD.org/src/commit/?id=ecaab0fb5da4cd7340c62b77bcb19efcfa1b69df

commit ecaab0fb5da4cd7340c62b77bcb19efcfa1b69df
Author:     Stephen J. Kiernan <stevek@FreeBSD.org>
AuthorDate: 2024-05-01 19:45:45 +0000
Commit:     Stephen J. Kiernan <stevek@FreeBSD.org>
CommitDate: 2024-05-01 19:45:45 +0000

    guestrpc module to handle VMware backdoor port GuestRPC functionality
    
    Convert existing FreeBSD vmware_hvcall function to take a channel
    and parameter arguments.
    
    Added vmware_guestrpc_cmd() to send GuestRPC commands to the VMware
    hypervisor. The sbuf argument is used for both the command to send
    and to store the data to return to the caller.
    
    The following KPIs can be used to get and set FreeBSD-specific guest
    information in key/value pairs:
     * vmware_guestrpc_set_guestinfo
       - set a value into the guestinfo.fbsd.<keyword> key
     * vmware_guestrpc_get_guestinfo
       - get the value stored in the guestinfo.fbsd.<keyword> key
    
    Add VMware devices to x86 NOTES
    
    Reviewed by:    jhb
    Obtained from:  Juniper Networks, Inc.
    Differential Revision:  https://reviews.freebsd.org/D44528
---
 sys/conf/files.x86                |   1 +
 sys/x86/acpica/madt.c             |   3 +-
 sys/x86/conf/NOTES                |   5 +
 sys/x86/include/vmware.h          |   8 +-
 sys/x86/include/vmware_guestrpc.h |  37 +++++
 sys/x86/x86/identcpu.c            |   3 +-
 sys/x86/x86/tsc.c                 |   2 +-
 sys/x86/x86/vmware_guestrpc.c     | 337 ++++++++++++++++++++++++++++++++++++++
 8 files changed, 391 insertions(+), 5 deletions(-)

diff --git a/sys/conf/files.x86 b/sys/conf/files.x86
index ce31c42215be..9439a46ce347 100644
--- a/sys/conf/files.x86
+++ b/sys/conf/files.x86
@@ -380,6 +380,7 @@ x86/x86/stack_machdep.c		optional	ddb | stack
 x86/x86/tsc.c			standard
 x86/x86/ucode.c			standard
 x86/x86/ucode_subr.c		standard
+x86/x86/vmware_guestrpc.c	optional	vmware_guestrpc
 x86/x86/delay.c			standard
 x86/xen/hvm.c			optional	xenhvm
 x86/xen/xen_apic.c		optional	xenhvm smp
diff --git a/sys/x86/acpica/madt.c b/sys/x86/acpica/madt.c
index adfeed70c5c6..c6358ad7e847 100644
--- a/sys/x86/acpica/madt.c
+++ b/sys/x86/acpica/madt.c
@@ -159,7 +159,8 @@ madt_x2apic_disable_reason(void)
 	}
 
 	if (vm_guest == VM_GUEST_VMWARE) {
-		vmware_hvcall(VMW_HVCMD_GETVCPU_INFO, p);
+		vmware_hvcall(0, VMW_HVCMD_GETVCPU_INFO,
+		    VMW_HVCMD_DEFAULT_PARAM, p);
 		if ((p[0] & VMW_VCPUINFO_VCPU_RESERVED) != 0 ||
 		    (p[0] & VMW_VCPUINFO_LEGACY_X2APIC) == 0)
 			return ("inside VMWare without intr redirection");
diff --git a/sys/x86/conf/NOTES b/sys/x86/conf/NOTES
index 5042585da310..87a8f8924d12 100644
--- a/sys/x86/conf/NOTES
+++ b/sys/x86/conf/NOTES
@@ -550,6 +550,11 @@ device		kvm_clock	# KVM paravirtual clock driver
 device 		hyperv		# HyperV drivers
 device		hvhid		# HyperV HID device
 
+# VMware hypervisor support
+device		pvscsi		# Paravirtual SCSI driver
+device		vmci		# Virtual Machine Communication Interface (VMCI)
+device		vmware_guestrpc	# GuestRPC interface
+
 #
 # Laptop/Notebook options:
 #
diff --git a/sys/x86/include/vmware.h b/sys/x86/include/vmware.h
index 38bca8c146a7..5ab6462b862d 100644
--- a/sys/x86/include/vmware.h
+++ b/sys/x86/include/vmware.h
@@ -31,19 +31,23 @@
 #define	VMW_HVPORT		0x5658
 
 #define	VMW_HVCMD_GETVERSION	10
+#define	VMW_HVCMD_GUESTRPC	30
 #define	VMW_HVCMD_GETHZ		45
 #define	VMW_HVCMD_GETVCPU_INFO	68
 
+#define	VMW_HVCMD_DEFAULT_PARAM	UINT_MAX
+
 #define	VMW_VCPUINFO_LEGACY_X2APIC	(1 << 3)
 #define	VMW_VCPUINFO_VCPU_RESERVED	(1 << 31)
 
 static __inline void
-vmware_hvcall(u_int cmd, u_int *p)
+vmware_hvcall(int chan, u_int cmd, u_int param, u_int *p)
 {
 
 	__asm __volatile("inl %w3, %0"
 	: "=a" (p[0]), "=b" (p[1]), "=c" (p[2]), "=d" (p[3])
-	: "0" (VMW_HVMAGIC), "1" (UINT_MAX), "2" (cmd), "3" (VMW_HVPORT)
+	: "0" (VMW_HVMAGIC), "1" (param), "2" (cmd),
+	  "3" (VMW_HVPORT | (chan << 16))
 	: "memory");
 }
 
diff --git a/sys/x86/include/vmware_guestrpc.h b/sys/x86/include/vmware_guestrpc.h
new file mode 100644
index 000000000000..24055b6c249e
--- /dev/null
+++ b/sys/x86/include/vmware_guestrpc.h
@@ -0,0 +1,37 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015-2024, Juniper Networks, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _X86_VMWARE_GUESTRPC_H_
+#define _X86_VMWARE_GUESTRPC_H_
+
+struct sbuf;
+
+int	vmware_guestrpc_cmd(struct sbuf *sbufp);
+int	vmware_guestrpc_set_guestinfo(const char *keyword, const char *val);
+int	vmware_guestrpc_get_guestinfo(const char *keyword, struct sbuf *sbufp);
+
+#endif /* _X86_VMWARE_GUESTRPC_H_ */
diff --git a/sys/x86/x86/identcpu.c b/sys/x86/x86/identcpu.c
index 919dda722d71..953736d6b25c 100644
--- a/sys/x86/x86/identcpu.c
+++ b/sys/x86/x86/identcpu.c
@@ -1470,7 +1470,8 @@ identify_hypervisor(void)
 	p = kern_getenv("smbios.system.serial");
 	if (p != NULL) {
 		if (strncmp(p, "VMware-", 7) == 0 || strncmp(p, "VMW", 3) == 0) {
-			vmware_hvcall(VMW_HVCMD_GETVERSION, regs);
+			vmware_hvcall(0, VMW_HVCMD_GETVERSION,
+			    VMW_HVCMD_DEFAULT_PARAM, regs);
 			if (regs[1] == VMW_HVMAGIC) {
 				vm_guest = VM_GUEST_VMWARE;
 				freeenv(p);
diff --git a/sys/x86/x86/tsc.c b/sys/x86/x86/tsc.c
index 7c11a1f5f300..4edaa37d9b54 100644
--- a/sys/x86/x86/tsc.c
+++ b/sys/x86/x86/tsc.c
@@ -139,7 +139,7 @@ tsc_freq_vmware(void)
 {
 	u_int regs[4];
 
-	vmware_hvcall(VMW_HVCMD_GETHZ, regs);
+	vmware_hvcall(0, VMW_HVCMD_GETHZ, VMW_HVCMD_DEFAULT_PARAM, regs);
 	if (regs[1] != UINT_MAX)
 		tsc_freq = regs[0] | ((uint64_t)regs[1] << 32);
 	tsc_early_calib_exact = 1;
diff --git a/sys/x86/x86/vmware_guestrpc.c b/sys/x86/x86/vmware_guestrpc.c
new file mode 100644
index 000000000000..b62f40c5a9ef
--- /dev/null
+++ b/sys/x86/x86/vmware_guestrpc.c
@@ -0,0 +1,337 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013-2024, Juniper Networks, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/limits.h>
+#include <sys/bus.h>
+#include <sys/sbuf.h>
+#include <sys/errno.h>
+#include <sys/module.h>
+
+#include <x86/vmware.h>
+#include <x86/vmware_guestrpc.h>
+
+/* GuestRPC Subcommands */
+#define		VMW_HVGUESTRPC_OPEN			0x00
+#define		VMW_HVGUESTRPC_SEND_LEN			0x01
+#define		VMW_HVGUESTRPC_SEND_DATA		0x02
+#define		VMW_HVGUESTRPC_RECV_LEN			0x03
+#define		VMW_HVGUESTRPC_RECV_DATA		0x04
+#define		VMW_HVGUESTRPC_FINISH_RECV		0x05
+#define		VMW_HVGUESTRPC_CLOSE			0x06
+/* GuestRPC Parameters */
+#define		VMW_HVGUESTRPC_OPEN_MAGIC		0x49435052
+/* GuestRPC Status */
+#define		VMW_HVGUESTRPC_FAILURE			0x00000000
+#define		VMW_HVGUESTRPC_OPEN_SUCCESS		0x00010000
+#define		VMW_HVGUESTRPC_SEND_LEN_SUCCESS		0x00810000
+#define		VMW_HVGUESTRPC_SEND_DATA_SUCCESS	0x00010000
+#define		VMW_HVGUESTRPC_RECV_LEN_SUCCESS		0x00830000
+#define		VMW_HVGUESTRPC_RECV_DATA_SUCCESS	0x00010000
+#define		VMW_HVGUESTRPC_FINISH_RECV_SUCCESS	0x00010000
+#define		VMW_HVGUESTRPC_CLOSE_SUCCESS		0x00010000
+
+#define	VMW_GUESTRPC_EBX(_p)	((_p)[1])
+#define	VMW_GUESTRPC_EDXHI(_p)	((_p)[3] >> 16)
+#define	VMW_GUESTRPC_STATUS(_p)	((_p)[2])
+
+static __inline void
+vmware_guestrpc(int chan, uint16_t subcmd, uint32_t param, u_int *p)
+{
+
+#ifdef DEBUG_VMGUESTRPC
+	printf("%s(%d, %#x, %#x, %p)\n", __func__, chan, subcmd, param, p);
+#endif
+	vmware_hvcall(chan, VMW_HVCMD_GUESTRPC | (subcmd << 16), param, p);
+#ifdef DEBUG_VMGUESTRPC
+	printf("p[0] = %#x\n", p[0]);
+	printf("p[1] = %#x\n", p[1]);
+	printf("p[2] = %#x\n", p[2]);
+	printf("p[3] = %#x\n", p[3]);
+#endif
+}
+
+/*
+ * Start a GuestRPC request
+ *
+ * Channel number is returned in the EDXHI parameter.
+ *
+ * This channel number must be used in successive GuestRPC requests for
+ * sending and receiving RPC data.
+ */
+static int
+vmware_guestrpc_open(void)
+{
+	u_int p[4];
+
+	vmware_guestrpc(0, VMW_HVGUESTRPC_OPEN, VMW_HVGUESTRPC_OPEN_MAGIC,
+	    p);
+	if (VMW_GUESTRPC_STATUS(p) != VMW_HVGUESTRPC_OPEN_SUCCESS)
+		return (-1);
+
+	return (VMW_GUESTRPC_EDXHI(p));
+}
+
+/*
+ * Send the length of the GuestRPC request
+ *
+ * In a GuestRPC request, the total length of the request must be sent
+ * before any data can be sent.
+ */
+static int
+vmware_guestrpc_send_len(int channel, size_t len)
+{
+	u_int p[4];
+
+	vmware_guestrpc(channel, VMW_HVGUESTRPC_SEND_LEN, len, p);
+	if (VMW_GUESTRPC_STATUS(p) != VMW_HVGUESTRPC_SEND_LEN_SUCCESS)
+		return (-1);
+
+	return (0);
+}
+
+/*
+ * Send the data for the GuestRPC request
+ *
+ * The total length of the GuestRPC request must be sent before any data.
+ * Data is sent 32-bit values at a time and therefore may require multiple
+ * calls to send all the data.
+ */
+static int
+vmware_guestrpc_send_data(int channel, uint32_t data)
+{
+	u_int p[4];
+
+	vmware_guestrpc(channel, VMW_HVGUESTRPC_SEND_DATA, data, p);
+	if (VMW_GUESTRPC_STATUS(p) != VMW_HVGUESTRPC_SEND_DATA_SUCCESS)
+		return (-1);
+
+	return (0);
+}
+
+/*
+ * Receive the length of the GuestRPC reply.
+ *
+ * Length of the reply data is returned in the EBX parameter.
+ * The reply identifier is returned in the EDXHI parameter.
+ *
+ * The reply identifier must be used as the GuestRPC parameter in calls
+ * to vmware_guestrpc_recv_data()
+ */
+static int
+vmware_guestrpc_recv_len(int channel, size_t *lenp)
+{
+	u_int p[4];
+
+	vmware_guestrpc(channel, VMW_HVGUESTRPC_RECV_LEN, 0, p);
+	if (VMW_GUESTRPC_STATUS(p) != VMW_HVGUESTRPC_RECV_LEN_SUCCESS)
+		return (-1);
+
+	*lenp = VMW_GUESTRPC_EBX(p);
+	return (VMW_GUESTRPC_EDXHI(p));
+}
+
+/*
+ * Receive the GuestRPC reply data.
+ *
+ * Data is received in 32-bit values at a time and therefore may
+ * require multiple requests to get all the data.
+ */
+static int
+vmware_guestrpc_recv_data(int channel, int id, uint32_t *datap)
+{
+	u_int p[4];
+
+	vmware_guestrpc(channel, VMW_HVGUESTRPC_RECV_DATA, id, p);
+	if (VMW_GUESTRPC_STATUS(p) != VMW_HVGUESTRPC_RECV_DATA_SUCCESS)
+		return (-1);
+
+	*datap = VMW_GUESTRPC_EBX(p);
+	return (0);
+}
+
+/*
+ * Close the GuestRPC channel.
+ */
+static int
+vmware_guestrpc_close(int channel)
+{
+	u_int p[4];
+
+	vmware_guestrpc(channel, VMW_HVGUESTRPC_CLOSE, 0, p);
+	if (VMW_GUESTRPC_STATUS(p) != VMW_HVGUESTRPC_CLOSE_SUCCESS)
+		return (-1);
+
+	return (0);
+}
+
+/*
+ * Send a GuestRPC command.
+ */
+int
+vmware_guestrpc_cmd(struct sbuf *sbufp)
+{
+	char *buf;
+	size_t cnt, len;
+	int chan, id, status;
+	uint32_t data;
+
+	/* Make sure we are running under VMware hypervisor */
+	if (vm_guest != VM_GUEST_VMWARE)
+		return (ENXIO);
+
+	/* Open the GuestRPC channel */
+	chan = vmware_guestrpc_open();
+	if (chan == -1)
+		return (EIO);
+
+	/* Send the length */
+	buf = sbuf_data(sbufp);
+	len = sbuf_len(sbufp);
+	status = vmware_guestrpc_send_len(chan, len);
+	if (status == -1)
+		goto done;
+
+	/* Send the data */
+	while (len > 0) {
+		data = 0;
+		cnt = min(4, len);
+		memcpy(&data, buf, cnt);
+		status = vmware_guestrpc_send_data(chan, data);
+		if (status == -1)
+			goto done;
+		buf += cnt;
+		len -= cnt;
+	}
+
+	/* Receive the length of the reply data */
+	id = vmware_guestrpc_recv_len(chan, &len);
+	if (id == -1)
+		goto done;
+
+	/* Receive the reply data */
+	sbuf_clear(sbufp);
+	while (len > 0) {
+		status = vmware_guestrpc_recv_data(chan, id, &data);
+		if (status == -1)
+			goto done;
+		sbuf_bcat(sbufp, &data, 4);
+		len -= min(4, len);
+	}
+
+done:
+	/* Close the GuestRPC channel */
+	vmware_guestrpc_close(chan);
+	return (status == -1 ? EIO : 0);
+}
+
+/*
+ * Set guest information key/value pair
+ */
+int
+vmware_guestrpc_set_guestinfo(const char *keyword, const char *val)
+{
+	struct sbuf sb;
+	char *buf;
+	int error;
+
+#ifdef DEBUG_VMGUESTRPC
+	printf("%s: %s=%s\n", __func__, keyword, val);
+#endif
+
+	/* Send "info-set" GuestRPC command */
+	sbuf_new(&sb, NULL, 256, SBUF_AUTOEXTEND);
+	sbuf_printf(&sb, "info-set guestinfo.fbsd.%s %s", keyword, val);
+	sbuf_trim(&sb);
+	sbuf_finish(&sb);
+
+	error = vmware_guestrpc_cmd(&sb);
+	if (error)
+		return (error);
+
+	sbuf_finish(&sb);
+	buf = sbuf_data(&sb);
+
+#ifdef DEBUG_VMGUESTRPC
+	printf("%s: result: %s\n", __func__, buf);
+#endif
+
+	/* Buffer will contain 1 on sucess or 0 on failure */
+	return ((buf[0] == '0') ? EINVAL : 0);
+}
+
+/*
+ * Get guest information key/value pair.
+ */
+int
+vmware_guestrpc_get_guestinfo(const char *keyword, struct sbuf *sbufp)
+{
+	struct sbuf sb;
+	char *buf;
+	int error;
+
+#ifdef DEBUG_VMGUESTRPC
+	printf("%s: %s\n", __func__, keyword);
+#endif
+
+	/* Send "info-get" GuestRPC command */
+	sbuf_new(&sb, NULL, 256, SBUF_AUTOEXTEND);
+	sbuf_printf(&sb, "info-get guestinfo.fbsd.%s", keyword);
+	sbuf_trim(&sb);
+	sbuf_finish(&sb);
+
+	error = vmware_guestrpc_cmd(&sb);
+	if (error)
+		return (error);
+
+	sbuf_finish(&sb);
+	buf = sbuf_data(&sb);
+
+#ifdef DEBUG_VMGUESTRPC
+	printf("%s: result: %s\n", __func__, buf);
+#endif
+
+	/*
+	 * Buffer will contain "1 <value>" on success or
+	 * "0 No value found" on failure
+	 */
+	if (buf[0] == '0')
+		return (ENOENT);
+
+	/*
+	 * Add value from buffer to the sbuf
+	 */
+	sbuf_cat(sbufp, buf + 2);
+	return (0);
+}
+
+MODULE_VERSION(vmware_guestrpc, 1);