toaruos/modules/vmware.c
2021-11-26 12:46:05 +09:00

523 lines
12 KiB
C

/**
* @file kernel/arch/x86_64/vmware.c
* @brief VMware/QEMU mouse and VMWare backdoor driver.
*
* Supports absolute mouse cursor and resolution setting.
*
* Mouse:
* Toggle off / on with ioctl 1 and 2 respectively to /dev/vmmouse.
* Supports mouse buttons, unlike the one in VirtualBox.
* This device is also available by default in QEMU.
*
* Resolution setting:
* Enabled when the "vmware" LFB driver is active. Automatically
* resizes the display when the window size changes.
*
* @copyright
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2017-2021 K. Lange
*/
#include <errno.h>
#include <stdint.h>
#include <stddef.h>
#include <kernel/vfs.h>
#include <kernel/string.h>
#include <kernel/printf.h>
#include <kernel/types.h>
#include <kernel/video.h>
#include <kernel/pipe.h>
#include <kernel/process.h>
#include <kernel/mouse.h>
#include <kernel/time.h>
#include <kernel/args.h>
#include <kernel/module.h>
#include <kernel/arch/x86_64/ports.h>
#define VMWARE_MAGIC 0x564D5868 /* hXMV */
#define VMWARE_PORT 0x5658
#define VMWARE_PORTHB 0x5659
#define PACKETS_IN_PIPE 1024
#define DISCARD_POINT 32
#define CMD_GETVERSION 10
#define CMD_MESSAGE 30
#define CMD_ABSPOINTER_DATA 39
#define CMD_ABSPOINTER_STATUS 40
#define CMD_ABSPOINTER_COMMAND 41
#define ABSPOINTER_ENABLE 0x45414552 /* Q E A E */
#define ABSPOINTER_RELATIVE 0xF5
#define ABSPOINTER_ABSOLUTE 0x53424152 /* R A B S */
#define MESSAGE_RPCI 0x49435052 /* R P C I */
#define MESSAGE_TCLO 0x4f4c4354 /* T C L O */
/* -Wpedantic complains about unnamed unions */
#pragma GCC diagnostic ignored "-Wpedantic"
extern void (*ps2_mouse_alternate)(uint8_t); /* modules/mouse.c */
static fs_node_t * mouse_pipe;
typedef struct {
union {
uint32_t ax;
uint32_t magic;
};
union {
uint32_t bx;
size_t size;
};
union {
uint32_t cx;
uint16_t command;
};
union {
uint32_t dx;
uint16_t port;
};
uintptr_t si;
uintptr_t di;
} vmware_cmd;
/** Low bandwidth backdoor */
static void vmware_send(vmware_cmd * cmd) {
cmd->magic = VMWARE_MAGIC;
cmd->port = VMWARE_PORT;
asm volatile("in %%dx, %0" : "+a"(cmd->ax), "+b"(cmd->bx), "+c"(cmd->cx), "+d"(cmd->dx), "+S"(cmd->si), "+D"(cmd->di));
}
/** Output to high bandwidth backdoor */
static void vmware_send_hb(vmware_cmd * cmd) {
cmd->magic = VMWARE_MAGIC;
cmd->port = VMWARE_PORTHB;
asm volatile("cld; rep; outsb" : "+a"(cmd->ax), "+b"(cmd->bx), "+c"(cmd->cx), "+d"(cmd->dx), "+S"(cmd->si), "+D"(cmd->di));
}
/** Input from high bandwidth backdoor */
static void vmware_get_hb(vmware_cmd * cmd) {
cmd->magic = VMWARE_MAGIC;
cmd->port = VMWARE_PORTHB;
asm volatile("cld; rep; insb" : "+a"(cmd->ax), "+b"(cmd->bx), "+c"(cmd->cx), "+d"(cmd->dx), "+S"(cmd->si), "+D"(cmd->di));
}
static void mouse_off(void) {
/* Disable the absolute mouse */
vmware_cmd cmd;
cmd.bx = ABSPOINTER_RELATIVE;
cmd.command = CMD_ABSPOINTER_COMMAND;
vmware_send(&cmd);
}
static void mouse_absolute(void) {
/*
* Set the mouse to absolute.
*
* You can also set a relative mode, but there's not
* a lot of use in that as disabling the device just
* falls back to the PS/2 (or USB, I guess) device anyway,
* so instead of using that we just... turn it off.
*/
vmware_cmd cmd;
/* Enable */
cmd.bx = ABSPOINTER_ENABLE;
cmd.command = CMD_ABSPOINTER_COMMAND;
vmware_send(&cmd);
/* Status */
cmd.bx = 0;
cmd.command = CMD_ABSPOINTER_STATUS;
vmware_send(&cmd);
/* Read data (1) */
cmd.bx = 1;
cmd.command = CMD_ABSPOINTER_DATA;
vmware_send(&cmd);
/* Enable absolute */
cmd.bx = ABSPOINTER_ABSOLUTE;
cmd.command = CMD_ABSPOINTER_COMMAND;
vmware_send(&cmd);
}
volatile int8_t vmware_mouse_byte = 0;
static void vmware_mouse(uint8_t byte) {
/* unused, but we need to read the fake mouse event bytes from the PS/2 device. */
vmware_mouse_byte = byte;
/* Read status byte. */
vmware_cmd cmd;
cmd.bx = 0;
cmd.command = CMD_ABSPOINTER_STATUS;
vmware_send(&cmd);
if (cmd.ax == 0xffff0000) {
/* Device error; turn it off and back on again. */
mouse_off();
mouse_absolute();
return;
}
int words = cmd.ax & 0xFFFF;
if (!words || words % 4) {
/* If we don't have data, or for some reason data isn't a multiple of 4... bail */
return;
}
/* Read 4 bytes of data */
cmd.bx = 4; /* how many */
cmd.command = CMD_ABSPOINTER_DATA; /* read */
vmware_send(&cmd);
/*
* I guess the flags tell you if this was relative or absolute, so if we
* actually used the relative mode, we'd want to check that, but...
*/
//int flags = (cmd.ax & 0xFFFF0000) >> 16;
int buttons = (cmd.ax & 0x0000FFFF);
unsigned int x = 0;
unsigned int y = 0;
if (lfb_vid_memory && lfb_resolution_x && lfb_resolution_y) {
/*
* Just like the virtualbox stuff, this is based on a mapping
* to the display resolution, independently scaled in
* each dimension...
*/
x = ((unsigned int)cmd.bx * lfb_resolution_x) / 0xFFFF;
y = ((unsigned int)cmd.cx * lfb_resolution_y) / 0xFFFF;
} else {
x = cmd.bx;
y = cmd.cx;
}
mouse_device_packet_t packet;
packet.magic = MOUSE_MAGIC;
packet.x_difference = x;
packet.y_difference = y;
packet.buttons = 0;
/* The particular bits for the buttons seem weird, but okay... */
if (buttons & 0x20) {
packet.buttons |= LEFT_CLICK;
}
if (buttons & 0x10) {
packet.buttons |= RIGHT_CLICK;
}
if (buttons & 0x08) {
packet.buttons |= MIDDLE_CLICK;
}
/* dx = z = scroll amount */
if ((int8_t)cmd.dx > 0) {
packet.buttons |= MOUSE_SCROLL_DOWN;
} else if ((int8_t)cmd.dx < 0) {
packet.buttons |= MOUSE_SCROLL_UP;
}
mouse_device_packet_t bitbucket;
while (pipe_size(mouse_pipe) > (int)(DISCARD_POINT * sizeof(packet))) {
read_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&bitbucket);
}
write_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&packet);
}
static int detect_device(void) {
vmware_cmd cmd;
/* read version */
cmd.bx = ~VMWARE_MAGIC;
cmd.command = CMD_GETVERSION;
vmware_send(&cmd);
if (cmd.bx != VMWARE_MAGIC || cmd.ax == 0xFFFFFFFF) {
/* Not a vmware device... */
return 0;
}
/* Good to go! */
return 1;
}
static int open_msg_channel(uint32_t proto) {
vmware_cmd cmd;
cmd.cx = CMD_MESSAGE | 0x00000000; /* CMD_MESSAGE */
cmd.bx = proto;
vmware_send(&cmd);
if ((cmd.cx & 0x10000) == 0) {
return -1;
}
return cmd.dx >> 16;
}
static void msg_close(int channel) {
vmware_cmd cmd = {0};
cmd.cx = CMD_MESSAGE | 0x00060000;
cmd.bx = 0;
cmd.dx = channel << 16;
vmware_send(&cmd);
}
static int open_rpci_channel(void) {
return open_msg_channel(MESSAGE_RPCI);
}
static int tclo_channel = -1;
static int open_tclo_channel(void) {
if (tclo_channel != -1) {
msg_close(tclo_channel);
}
tclo_channel = open_msg_channel(MESSAGE_TCLO);
return tclo_channel;
}
static int msg_send(int channel, const char * msg, size_t size) {
{
vmware_cmd cmd = {0};
cmd.cx = CMD_MESSAGE | 0x00010000; /* CMD_MESSAGE size */
cmd.size = size;
cmd.dx = channel << 16;
vmware_send(&cmd);
if (size == 0) return 0;
if (((cmd.cx >> 16) & 0x0081) != 0x0081) {
return -2;
}
}
{
vmware_cmd cmd = {0};
cmd.bx = 0x0010000;
cmd.cx = size;
cmd.dx = channel << 16;
cmd.si = (uintptr_t)msg;
vmware_send_hb(&cmd);
if (!(cmd.bx & 0x0010000)) {
return -3;
}
}
return 0;
}
static int msg_recv(int channel, char * buf, size_t bufsize) {
size_t size;
{
vmware_cmd cmd = {0};
cmd.cx = CMD_MESSAGE | 0x00030000; /* CMD_MESSAGE receive ize */
cmd.dx = channel << 16;
vmware_send(&cmd);
size = cmd.bx;
if (size == 0) return 0;
if (((cmd.cx >> 16) & 0x0083) != 0x0083) {
return -2;
}
if (size > bufsize) return -1;
}
{
vmware_cmd cmd = {0};
cmd.bx = 0x00010000;
cmd.cx = size;
cmd.dx = channel << 16;
cmd.di = (uintptr_t)buf;
vmware_get_hb(&cmd);
if (!(cmd.bx & 0x00010000)) {
return -3;
}
}
{
vmware_cmd cmd = {0};
cmd.cx = CMD_MESSAGE | 0x00050000;
cmd.bx = 0x0001;
cmd.dx = channel << 16;
vmware_send(&cmd);
}
return size;
}
static int rpci_string(const char * request) {
/* Open channel */
int channel = open_rpci_channel();
if (channel < 0) return channel;
size_t size = strlen(request) + 1;
msg_send(channel, request, size);
char buf[16];
int recv_size = msg_recv(channel, buf, 16);
msg_close(channel);
if (recv_size < 0) return recv_size;
return 0;
}
static int attempt_scale(void) {
int i;
int c = open_tclo_channel();
if (c < 0) {
return 1;
}
char buf[256];
if ((i = msg_send(c, buf, 0)) < 0) { return 1; }
int resend = 0;
while (1) {
i = msg_recv(c, buf, 256);
if (i < 0) {
return 1;
} else if (i == 0) {
if (resend) {
if ((i = rpci_string("tools.capability.resolution_set 1")) < 0) { return 1; }
if ((i = rpci_string("tools.capability.resolution_server toolbox 1")) < 0) { return 1; }
if ((i = rpci_string("tools.capability.display_topology_set 1")) < 0) { return 1; }
if ((i = rpci_string("tools.capability.color_depth_set 1")) < 0) { return 1; }
if ((i = rpci_string("tools.capability.resolution_min 0 0")) < 0) { return 1; }
if ((i = rpci_string("tools.capability.unity 1")) < 0) { return 1; }
resend = 0;
} else {
unsigned long s, ss;
relative_time(0, 10000, &s, &ss);
sleep_until((process_t *)this_core->current_process, s, ss);
switch_task(0);
}
if ((i = msg_send(c, buf, 0)) < 0) { return 1; }
} else {
buf[i] = '\0';
if (startswith(buf, "reset")) {
if ((i = msg_send(c, "OK ATR toolbox", strlen("OK ATR toolbox"))) < 0) {
return 1;
}
} else if (startswith(buf, "ping")) {
if ((i = msg_send(c, "OK ", strlen("OK "))) < 0) {
return 1;
}
} else if (startswith(buf, "Capabilities_Register")) {
if ((i = msg_send(c, "OK ", strlen("OK "))) < 0) {
return 1;
}
resend = 1;
} else if (startswith(buf, "Resolution_Set")) {
char * x = &buf[15];
char * y = strstr(x," ");
if (!y) {
return 1;
}
*y = '\0';
y++;
int _x = atoi(x);
int _y = atoi(y);
if (lfb_resolution_x && _x && (_x != lfb_resolution_x || _y != lfb_resolution_y)) {
lfb_set_resolution(_x, _y);
}
if ((i = msg_send(c, "OK ", strlen("OK "))) < 0) {
return 1;
}
msg_close(c);
return 0;
} else {
if ((i = msg_send(c, "ERROR Unknown command", strlen("ERROR Unknown command"))) < 0) {
return 1;
}
}
}
}
}
static void vmware_resize(void * data) {
while (1) {
attempt_scale();
unsigned long s, ss;
relative_time(1, 0, &s, &ss);
sleep_until((process_t *)this_core->current_process, s, ss);
switch_task(0);
}
}
static int ioctl_mouse(fs_node_t * node, unsigned long request, void * argp) {
switch (request) {
case 1:
/* Disable */
mouse_off();
ps2_mouse_alternate = NULL;
return 0;
case 2:
/* Enable */
ps2_mouse_alternate = vmware_mouse;
mouse_absolute();
return 0;
case 3:
return ps2_mouse_alternate == vmware_mouse;
default:
return -EINVAL;
}
}
static int vmware_initialize(int argc, char * argv[]) {
if (!detect_device()) return -ENODEV;
mouse_pipe = make_pipe(sizeof(mouse_device_packet_t) * PACKETS_IN_PIPE);
mouse_pipe->flags = FS_CHARDEVICE;
vfs_mount("/dev/vmmouse", mouse_pipe);
mouse_pipe->flags = FS_CHARDEVICE;
mouse_pipe->ioctl = ioctl_mouse;
/*
* We have a hack in the PS/2 mouse driver that lets us
* take over for the normal mouse driver and essential
* intercept the interrputs when they are valid.
*/
ps2_mouse_alternate = vmware_mouse;
mouse_absolute();
if (lfb_driver_name && !strcmp(lfb_driver_name, "vmware") && !args_present("novmwareresset")) {
spawn_worker_thread(vmware_resize, "[vmware]", NULL);
}
return 0;
}
static int fini(void) {
return 0;
}
struct Module metadata = {
.name = "vmware",
.init = vmware_initialize,
.fini = fini,
};