// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright 2016 VMware, Inc., Palo Alto, CA., USA
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sub license, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include <linux/objtool.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/cc_platform.h>
#include <asm/hypervisor.h>
#include <drm/drm_ioctl.h>
#include "vmwgfx_drv.h"
#include "vmwgfx_msg_x86.h"
#include "vmwgfx_msg_arm64.h"
#include "vmwgfx_mksstat.h"
#define MESSAGE_STATUS_SUCCESS 0x0001
#define MESSAGE_STATUS_DORECV 0x0002
#define MESSAGE_STATUS_CPT 0x0010
#define MESSAGE_STATUS_HB 0x0080
#define RPCI_PROTOCOL_NUM 0x49435052
#define GUESTMSG_FLAG_COOKIE 0x80000000
#define RETRIES 3
#define VMW_HYPERVISOR_MAGIC 0x564D5868
#define VMW_PORT_CMD_MSG 30
#define VMW_PORT_CMD_HB_MSG 0
#define VMW_PORT_CMD_OPEN_CHANNEL (MSG_TYPE_OPEN << 16 | VMW_PORT_CMD_MSG)
#define VMW_PORT_CMD_CLOSE_CHANNEL (MSG_TYPE_CLOSE << 16 | VMW_PORT_CMD_MSG)
#define VMW_PORT_CMD_SENDSIZE (MSG_TYPE_SENDSIZE << 16 | VMW_PORT_CMD_MSG)
#define VMW_PORT_CMD_RECVSIZE (MSG_TYPE_RECVSIZE << 16 | VMW_PORT_CMD_MSG)
#define VMW_PORT_CMD_RECVSTATUS (MSG_TYPE_RECVSTATUS << 16 | VMW_PORT_CMD_MSG)
#define VMW_PORT_CMD_MKS_GUEST_STATS 85
#define VMW_PORT_CMD_MKSGS_RESET (0 << 16 | VMW_PORT_CMD_MKS_GUEST_STATS)
#define VMW_PORT_CMD_MKSGS_ADD_PPN (1 << 16 | VMW_PORT_CMD_MKS_GUEST_STATS)
#define VMW_PORT_CMD_MKSGS_REMOVE_PPN (2 << 16 | VMW_PORT_CMD_MKS_GUEST_STATS)
#define HIGH_WORD(X) ((X & 0xFFFF0000) >> 16)
#define MAX_USER_MSG_LENGTH PAGE_SIZE
static u32 vmw_msg_enabled = 1;
enum rpc_msg_type {
MSG_TYPE_OPEN,
MSG_TYPE_SENDSIZE,
MSG_TYPE_SENDPAYLOAD,
MSG_TYPE_RECVSIZE,
MSG_TYPE_RECVPAYLOAD,
MSG_TYPE_RECVSTATUS,
MSG_TYPE_CLOSE,
};
struct rpc_channel {
u16 channel_id;
u32 cookie_high;
u32 cookie_low;
};
#if IS_ENABLED(CONFIG_DRM_VMWGFX_MKSSTATS)
/* Kernel mksGuestStats counter names and desciptions; same order as enum mksstat_kern_stats_t */
static const char* const mksstat_kern_name_desc[MKSSTAT_KERN_COUNT][2] =
{
{ "vmw_execbuf_ioctl", "vmw_execbuf_ioctl" },
{ "vmw_cotable_resize", "vmw_cotable_resize" },
};
#endif
/**
* vmw_open_channel
*
* @channel: RPC channel
* @protocol:
*
* Returns: 0 on success
*/
static int vmw_open_channel(struct rpc_channel *channel, unsigned int protocol)
{
unsigned long eax, ebx, ecx, edx, si = 0, di = 0;
VMW_PORT(VMW_PORT_CMD_OPEN_CHANNEL,
(protocol | GUESTMSG_FLAG_COOKIE), si, di,
0,
VMW_HYPERVISOR_MAGIC,
eax, ebx, ecx, edx, si, di);
if ((HIGH_WORD(ecx) & MESSAGE_STATUS_SUCCESS) == 0)
return -EINVAL;
channel->channel_id = HIGH_WORD(edx);
channel->cookie_high = si;
channel->cookie_low = di;
return 0;
}
/**
* vmw_close_channel
*
* @channel: RPC channel
*
* Returns: 0 on success
*/
static int vmw_close_channel(struct rpc_channel *channel)
{
unsigned long eax, ebx, ecx, edx, si, di;
/* Set up additional parameters */
si = channel->cookie_high;
di = channel->cookie_low;
VMW_PORT(VMW_PORT_CMD_CLOSE_CHANNEL,
0, si, di,
channel->channel_id << 16,
VMW_HYPERVISOR_MAGIC,
eax, ebx, ecx, edx, si, di);
if ((HIGH_WORD(ecx) & MESSAGE_STATUS_SUCCESS) == 0)
return -EINVAL;
return 0;
}
/**
* vmw_port_hb_out - Send the message payload either through the
* high-bandwidth port if available, or through the backdoor otherwise.
* @channel: The rpc channel.
* @msg: NULL-terminated message.
* @hb: Whether the high-bandwidth port is available.
*
* Return: The port status.
*/
static unsigned long vmw_port_hb_out(struct rpc_channel *channel,
const char *msg, bool hb)
{
unsigned long si, di, eax, ebx, ecx, edx;
unsigned long msg_len = strlen(msg);
/* HB port can't access encrypted memory. */
if (hb && !cc_platform_has(CC_ATTR_MEM_ENCRYPT)) {
unsigned long bp = channel->cookie_high;
u32 channel_id = (channel->channel_id << 16);
si = (uintptr_t) msg;
di = channel->cookie_