1 // SPDX-License-Identifier: GPL-2.0 OR MIT
3 * Copyright 2016 VMware, Inc., Palo Alto, CA., USA
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sub license, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
13 * The above copyright notice and this permission notice (including the
14 * next paragraph) shall be included in all copies or substantial portions
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
20 * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
21 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
23 * USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #include <linux/slab.h>
29 #include <linux/module.h>
30 #include <linux/kernel.h>
31 #include <linux/frame.h>
32 #include <asm/hypervisor.h>
34 #include "vmwgfx_drv.h"
35 #include "vmwgfx_msg.h"
38 #define MESSAGE_STATUS_SUCCESS 0x0001
39 #define MESSAGE_STATUS_DORECV 0x0002
40 #define MESSAGE_STATUS_CPT 0x0010
41 #define MESSAGE_STATUS_HB 0x0080
43 #define RPCI_PROTOCOL_NUM 0x49435052
44 #define GUESTMSG_FLAG_COOKIE 0x80000000
48 #define VMW_HYPERVISOR_MAGIC 0x564D5868
50 #define VMW_PORT_CMD_MSG 30
51 #define VMW_PORT_CMD_HB_MSG 0
52 #define VMW_PORT_CMD_OPEN_CHANNEL (MSG_TYPE_OPEN << 16 | VMW_PORT_CMD_MSG)
53 #define VMW_PORT_CMD_CLOSE_CHANNEL (MSG_TYPE_CLOSE << 16 | VMW_PORT_CMD_MSG)
54 #define VMW_PORT_CMD_SENDSIZE (MSG_TYPE_SENDSIZE << 16 | VMW_PORT_CMD_MSG)
55 #define VMW_PORT_CMD_RECVSIZE (MSG_TYPE_RECVSIZE << 16 | VMW_PORT_CMD_MSG)
56 #define VMW_PORT_CMD_RECVSTATUS (MSG_TYPE_RECVSTATUS << 16 | VMW_PORT_CMD_MSG)
58 #define HIGH_WORD(X) ((X & 0xFFFF0000) >> 16)
60 static u32 vmw_msg_enabled = 1;
83 * @channel: RPC channel
86 * Returns: 0 on success
88 static int vmw_open_channel(struct rpc_channel *channel, unsigned int protocol)
90 unsigned long eax, ebx, ecx, edx, si = 0, di = 0;
92 VMW_PORT(VMW_PORT_CMD_OPEN_CHANNEL,
93 (protocol | GUESTMSG_FLAG_COOKIE), si, di,
96 eax, ebx, ecx, edx, si, di);
98 if ((HIGH_WORD(ecx) & MESSAGE_STATUS_SUCCESS) == 0)
101 channel->channel_id = HIGH_WORD(edx);
102 channel->cookie_high = si;
103 channel->cookie_low = di;
113 * @channel: RPC channel
115 * Returns: 0 on success
117 static int vmw_close_channel(struct rpc_channel *channel)
119 unsigned long eax, ebx, ecx, edx, si, di;
121 /* Set up additional parameters */
122 si = channel->cookie_high;
123 di = channel->cookie_low;
125 VMW_PORT(VMW_PORT_CMD_CLOSE_CHANNEL,
127 channel->channel_id << 16,
128 VMW_HYPERVISOR_MAGIC,
129 eax, ebx, ecx, edx, si, di);
131 if ((HIGH_WORD(ecx) & MESSAGE_STATUS_SUCCESS) == 0)
138 * vmw_port_hb_out - Send the message payload either through the
139 * high-bandwidth port if available, or through the backdoor otherwise.
140 * @channel: The rpc channel.
141 * @msg: NULL-terminated message.
142 * @hb: Whether the high-bandwidth port is available.
144 * Return: The port status.
146 static unsigned long vmw_port_hb_out(struct rpc_channel *channel,
147 const char *msg, bool hb)
149 unsigned long si, di, eax, ebx, ecx, edx;
150 unsigned long msg_len = strlen(msg);
153 unsigned long bp = channel->cookie_high;
155 si = (uintptr_t) msg;
156 di = channel->cookie_low;
159 (MESSAGE_STATUS_SUCCESS << 16) | VMW_PORT_CMD_HB_MSG,
161 VMWARE_HYPERVISOR_HB | (channel->channel_id << 16) |
162 VMWARE_HYPERVISOR_OUT,
163 VMW_HYPERVISOR_MAGIC, bp,
164 eax, ebx, ecx, edx, si, di);
169 /* HB port not available. Send the message 4 bytes at a time. */
170 ecx = MESSAGE_STATUS_SUCCESS << 16;
171 while (msg_len && (HIGH_WORD(ecx) & MESSAGE_STATUS_SUCCESS)) {
172 unsigned int bytes = min_t(size_t, msg_len, 4);
173 unsigned long word = 0;
175 memcpy(&word, msg, bytes);
178 si = channel->cookie_high;
179 di = channel->cookie_low;
181 VMW_PORT(VMW_PORT_CMD_MSG | (MSG_TYPE_SENDPAYLOAD << 16),
183 channel->channel_id << 16,
184 VMW_HYPERVISOR_MAGIC,
185 eax, ebx, ecx, edx, si, di);
192 * vmw_port_hb_in - Receive the message payload either through the
193 * high-bandwidth port if available, or through the backdoor otherwise.
194 * @channel: The rpc channel.
195 * @reply: Pointer to buffer holding reply.
196 * @reply_len: Length of the reply.
197 * @hb: Whether the high-bandwidth port is available.
199 * Return: The port status.
201 static unsigned long vmw_port_hb_in(struct rpc_channel *channel, char *reply,
202 unsigned long reply_len, bool hb)
204 unsigned long si, di, eax, ebx, ecx, edx;
207 unsigned long bp = channel->cookie_low;
209 si = channel->cookie_high;
210 di = (uintptr_t) reply;
213 (MESSAGE_STATUS_SUCCESS << 16) | VMW_PORT_CMD_HB_MSG,
215 VMWARE_HYPERVISOR_HB | (channel->channel_id << 16),
216 VMW_HYPERVISOR_MAGIC, bp,
217 eax, ebx, ecx, edx, si, di);
222 /* HB port not available. Retrieve the message 4 bytes at a time. */
223 ecx = MESSAGE_STATUS_SUCCESS << 16;
225 unsigned int bytes = min_t(unsigned long, reply_len, 4);
227 si = channel->cookie_high;
228 di = channel->cookie_low;
230 VMW_PORT(VMW_PORT_CMD_MSG | (MSG_TYPE_RECVPAYLOAD << 16),
231 MESSAGE_STATUS_SUCCESS, si, di,
232 channel->channel_id << 16,
233 VMW_HYPERVISOR_MAGIC,
234 eax, ebx, ecx, edx, si, di);
236 if ((HIGH_WORD(ecx) & MESSAGE_STATUS_SUCCESS) == 0)
239 memcpy(reply, &ebx, bytes);
249 * vmw_send_msg: Sends a message to the host
251 * @channel: RPC channel
252 * @logmsg: NULL terminated string
254 * Returns: 0 on success
256 static int vmw_send_msg(struct rpc_channel *channel, const char *msg)
258 unsigned long eax, ebx, ecx, edx, si, di;
259 size_t msg_len = strlen(msg);
262 while (retries < RETRIES) {
265 /* Set up additional parameters */
266 si = channel->cookie_high;
267 di = channel->cookie_low;
269 VMW_PORT(VMW_PORT_CMD_SENDSIZE,
271 channel->channel_id << 16,
272 VMW_HYPERVISOR_MAGIC,
273 eax, ebx, ecx, edx, si, di);
275 if ((HIGH_WORD(ecx) & MESSAGE_STATUS_SUCCESS) == 0) {
276 /* Expected success. Give up. */
281 ebx = vmw_port_hb_out(channel, msg,
282 !!(HIGH_WORD(ecx) & MESSAGE_STATUS_HB));
284 if ((HIGH_WORD(ebx) & MESSAGE_STATUS_SUCCESS) != 0) {
286 } else if ((HIGH_WORD(ebx) & MESSAGE_STATUS_CPT) != 0) {
287 /* A checkpoint occurred. Retry. */
296 STACK_FRAME_NON_STANDARD(vmw_send_msg);
300 * vmw_recv_msg: Receives a message from the host
302 * Note: It is the caller's responsibility to call kfree() on msg.
304 * @channel: channel opened by vmw_open_channel
305 * @msg: [OUT] message received from the host
306 * @msg_len: message length
308 static int vmw_recv_msg(struct rpc_channel *channel, void **msg,
311 unsigned long eax, ebx, ecx, edx, si, di;
320 while (retries < RETRIES) {
323 /* Set up additional parameters */
324 si = channel->cookie_high;
325 di = channel->cookie_low;
327 VMW_PORT(VMW_PORT_CMD_RECVSIZE,
329 channel->channel_id << 16,
330 VMW_HYPERVISOR_MAGIC,
331 eax, ebx, ecx, edx, si, di);
333 if ((HIGH_WORD(ecx) & MESSAGE_STATUS_SUCCESS) == 0) {
334 DRM_ERROR("Failed to get reply size for host message.\n");
338 /* No reply available. This is okay. */
339 if ((HIGH_WORD(ecx) & MESSAGE_STATUS_DORECV) == 0)
343 reply = kzalloc(reply_len + 1, GFP_KERNEL);
345 DRM_ERROR("Cannot allocate memory for host message reply.\n");
351 ebx = vmw_port_hb_in(channel, reply, reply_len,
352 !!(HIGH_WORD(ecx) & MESSAGE_STATUS_HB));
353 if ((HIGH_WORD(ebx) & MESSAGE_STATUS_SUCCESS) == 0) {
356 if ((HIGH_WORD(ebx) & MESSAGE_STATUS_CPT) != 0) {
357 /* A checkpoint occurred. Retry. */
364 reply[reply_len] = '\0';
368 si = channel->cookie_high;
369 di = channel->cookie_low;
371 VMW_PORT(VMW_PORT_CMD_RECVSTATUS,
372 MESSAGE_STATUS_SUCCESS, si, di,
373 channel->channel_id << 16,
374 VMW_HYPERVISOR_MAGIC,
375 eax, ebx, ecx, edx, si, di);
377 if ((HIGH_WORD(ecx) & MESSAGE_STATUS_SUCCESS) == 0) {
380 if ((HIGH_WORD(ecx) & MESSAGE_STATUS_CPT) != 0) {
381 /* A checkpoint occurred. Retry. */
394 *msg_len = reply_len;
399 STACK_FRAME_NON_STANDARD(vmw_recv_msg);
403 * vmw_host_get_guestinfo: Gets a GuestInfo parameter
405 * Gets the value of a GuestInfo.* parameter. The value returned will be in
406 * a string, and it is up to the caller to post-process.
408 * @guest_info_param: Parameter to get, e.g. GuestInfo.svga.gl3
409 * @buffer: if NULL, *reply_len will contain reply size.
410 * @length: size of the reply_buf. Set to size of reply upon return
412 * Returns: 0 on success
414 int vmw_host_get_guestinfo(const char *guest_info_param,
415 char *buffer, size_t *length)
417 struct rpc_channel channel;
418 char *msg, *reply = NULL;
419 size_t reply_len = 0;
421 if (!vmw_msg_enabled)
424 if (!guest_info_param || !length)
427 msg = kasprintf(GFP_KERNEL, "info-get %s", guest_info_param);
429 DRM_ERROR("Cannot allocate memory to get guest info \"%s\".",
434 if (vmw_open_channel(&channel, RPCI_PROTOCOL_NUM))
437 if (vmw_send_msg(&channel, msg) ||
438 vmw_recv_msg(&channel, (void *) &reply, &reply_len))
441 vmw_close_channel(&channel);
442 if (buffer && reply && reply_len > 0) {
443 /* Remove reply code, which are the first 2 characters of
446 reply_len = max(reply_len - 2, (size_t) 0);
447 reply_len = min(reply_len, *length);
450 memcpy(buffer, reply + 2, reply_len);
461 vmw_close_channel(&channel);
466 DRM_ERROR("Failed to get guest info \"%s\".", guest_info_param);
474 * vmw_host_log: Sends a log message to the host
476 * @log: NULL terminated string
478 * Returns: 0 on success
480 int vmw_host_log(const char *log)
482 struct rpc_channel channel;
487 if (!vmw_msg_enabled)
493 msg = kasprintf(GFP_KERNEL, "log %s", log);
495 DRM_ERROR("Cannot allocate memory for host log message.\n");
499 if (vmw_open_channel(&channel, RPCI_PROTOCOL_NUM))
502 if (vmw_send_msg(&channel, msg))
505 vmw_close_channel(&channel);
511 vmw_close_channel(&channel);
514 DRM_ERROR("Failed to send host log message.\n");