523 lines
12 KiB
C
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,
|
|
};
|
|
|